策略模式
前言:设计一款模拟鸭子游戏,在游戏中会出现各种各样的鸭子,这些鸭子会游泳戏水,会叫,接下来定义鸭子的超类——Duck,并让各种鸭子继承此超类
Duck类
public class Duck {
//叫
public void quack(){
System.out.println("鸭子会嘎嘎嘎叫");
}
//游泳
public void swim(){
System.out.println("鸭子会游泳");
}
//鸭子外观
public void display(){
}
//鸭子的其他方法...
}
然后创建两个鸭子的子类RedHreadDuck(红头鸭),MallardDuck(绿头鸭)
public class RedHeadDuck extends Duck{
//覆盖,更改外观
@Override
public void display() {
System.out.println("红头鸭的外观是红头");
}
}
public class MallardDuck extends Duck{
//覆盖,更改外观
@Override
public void display() {
System.out.println("绿头鸭的外观是绿头");
}
}
接下来我们需要让鸭子飞,那么就需要在Duck类中添加fly() 方法,然后所有Duck子类都会继承 fly() 方法
public class Duck {
//鸭子其他方法...
//鸭子飞的方法
public void fly(){
System.out.println("鸭子会飞");
}
}
然后又新增了一个Duck两个子类——RubberDuck(橡皮鸭),DecoyDuck(诱饵鸭),这时候就出现问题了,橡皮鸭是不会飞的,但是会叫,而诱饵鸭既不会飞也不会叫,我们只能覆盖这些方法让它们什么都不做
public class RubberDuck extends Duck{
//覆盖橡皮鸭的方法变成吱吱吱叫
@Override
public void quack() {
System.out.println("橡皮鸭吱吱吱叫");
}
//覆盖,更改外观
@Override
public void display() {
System.out.println("外观是橡皮鸭");
}
//覆盖啥都不做
@Override
public void fly() {
//只能啥都不做,因为橡皮鸭不会飞
}
}
public class DecoyDuck extends Duck{
//覆盖,啥都不做
@Override
public void quack() {
//诱饵鸭不会叫
}
//覆盖,更改外观
@Override
public void display() {
System.out.println("外观是诱饵鸭");
}
//覆盖,啥都不做
@Override
public void fly() {
//诱饵鸭不会飞
}
}
我们利用继承来提供Duck的行为会有很多的缺点:
1、代码在多个子类中重复
2、很难知道所有鸭子的全部行为
3、鸭子不能同时又叫又会飞
4、改变会牵一发动全身,如后面在Duck中加入的fly()方法
5、运行时的行为不容易改变 等等
我们想到用接口把fly() 从 Duck类中取出来,放在一个Flyable接口中这样一来只有会飞的鸭子才实现此接口,同样的道理,也设计一个Quackable接口,因为不是所有的鸭子都会叫
但是我们后面会发现,虽然Flyable与Quackable可以解决“一部分“的问题(不会再有会飞的橡皮鸭什么的),但是也造成了代码无法复用,这就意味着无论何时你需要修改某个行为,你必须往下追踪并在每一个定义此行为的类中修改它,一不小心可能会造成新的错误,这只能算是从一个噩梦中跳到了另外一个噩梦。而且在会飞的鸭子中,飞行的动作可能还有很多种动作…
接下来我们的设计模式就要登场了,我们使用策略模式就能很好的解决这个问题。
设计原则:
- 找出应用中可能需要变化的地方,把它们独立出来,不要和那些不需要变换的代码混在一起。
- 针对接口编程,而不是实现类编程
- 多用组合,少用继承
下面我们定义两个接口,FlyBehavior,QuackBehavior。所有的飞行类都需要实现FlyBehavior接口,新的飞行类都需要实现fly() 方法,呱呱叫的行为也是一样,一个接口只包含一个需要实现的 quack() 方法。
这样设计可以让飞行和嘎嘎叫的动作被其他的对象复用,因为这些行为已经和Duck类无关了。而且我们可以新增一些行为,不会影响到既有的行为类,也不会影响“使用到”飞行行为的Duck类。
编写接口FlyBehavior,QuackBehavior
public interface FlyBehavior {
void fly();
}
public interface QuackBehavior {
void quack();
}
然后我们编写几个它们的实现类
public class FlyWithWings implements FlyBehavior{
@Override
public void fly() {
System.out.println("会飞");
}
}
public class FlyNotWay implements FlyBehavior{
@Override
public void fly() {
//什么都不做
System.out.println("不会飞");
}
}
public class Quack implements QuackBehavior{
@Override
public void quack() {
System.out.println("嘎嘎叫");
}
}
public class Squeak implements QuackBehavior{
@Override
public void quack() {
System.out.println("吱吱叫");
}
}
public class MuteQuack implements QuackBehavior{
@Override
public void quack() {
//什么都不做
System.out.println("不会叫");
}
}
接下我们就来整合鸭子的行为了
首先,在Duck类里面加入两个实例变量,分别是flyBehavior与quackBehavior,声明为接口类型(不是具体的实例对象类型),每只鸭子对象都会动态的设置这些变量以在运行时引用正确的行为类型,
我们还需要将Duck类中的fly()与quack() 删除,因为这些行为都被我们搬到FlyBehavior与QuackBehavior类中去了
我们将用两个相似的方法performFly和performQuack取代Duck类中的 fly() 与 quack() 方法。后面你就知道为什么这么做了
public abstract class Duck {
QuackBehavior quackBehavior; //每只鸭子都会引用实现QuackBehavior的对象
FlyBehavior flyBehavior; //每只鸭子都会引用实现FlyBehavior的对象
// 还有更多...
public void performQuack(){
quackBehavior.quack(); //鸭子对象不亲自处理嘎嘎叫的行为,而是委托给quackBehavior引用对象
}
public void performFly(){
flyBehavior.fly(); //鸭子对象不亲自处理飞行的行为,而是委托给flyBehavior引用对象
}
// 还有更多
// 游泳
public void swim(){
System.out.println("会游泳");
}
// 鸭子外观
public void display(){
}
// 鸭子的其他方法...
}
我们想要嘎嘎叫的动作,Duck对象只需要叫quackBehavior对象去嘎嘎叫就好了,在这些部分代码中,我们不在乎quackBehavior接口的对象到底是什么,我们只关心该对象如何进行嘎嘎叫就好了。
现在我们来关心如何设定flyBehavior和quackBehavior的实例变量
我们看看MallardDuck类
public class MallardDuck extends Duck{
public MallardDuck(){
/*
绿头鸭使用Quack类处理嘎嘎叫,调用父类的performQuack()方法时,
叫的职责会委托给Quack对象,然后我们得到了真正的嘎嘎叫
*/
quackBehavior = new Quack();
flyBehavior = new FlyWithWings(); //这个如上
}
@Override
public void display() {
System.out.println("我是绿头鸭");
}
}
还有RubberDuck
public class RubberDuck extends Duck {
public RubberDuck(){
quackBehavior = new Squeak();
flyBehavior = new FlyNotWay();
}
@Override
public void display() {
System.out.println("我是橡皮鸭");
}
}
接下来我们编写测试类看看我们的效果
public class TestDuck {
public static void main(String[] args) {
//绿头鸭
Duck mallard = new MallardDuck();
mallard.display();
mallard.performFly();
mallard.performQuack();
System.out.println("===========");
//橡皮鸭
Duck rubber = new RubberDuck();
rubber.display();
rubber.performFly();
rubber.performQuack();
}
}
结果:
绿头鸭使用Quack类处理嘎嘎叫,调用performQuack() 方法时,叫的职责会委托给Quack对象,然后我们得到了真正的嘎嘎叫。而调用performFly() 方法时,飞的动作也会委托给FlyWithWings对象,然后我们得到会飞的动作,我们的橡皮鸭也是一样的道理
我们在Duck类中创建了一堆的动态功能没有用到,接下来我们用一个‘set’方法来设定鸭子的行为,而不是在鸭子的构造器中实例化
在Duck类中加入两个新的方法:
public void setFlyBehavior(FlyBehavior flyBehavior){
this.flyBehavior = flyBehavior;
}
public void setQuackBehavior(QuackBehavior quackBehavior){
this.quackBehavior = quackBehavior;
}
然后我们新建一个鸭子类型:ModelDuck(模型鸭)
public class ModelDuck extends Duck{
public ModelDuck() {
flyBehavior = new FlyNotWay(); //设置模型鸭开始不会飞
quackBehavior = new Quack();
}
@Override
public void display() {
System.out.println("我是模型鸭");
}
}
现在模型鸭是不会飞的,我们想要模型鸭飞,我们就需要利用一个火箭动力的飞行行为(FlyRocketPowered),接下来我们来创建这个类,然后实现FlyBehavior接口
public class FlyRocketPowered implements FlyBehavior{
@Override
public void fly() {
System.out.println("添加了火箭动力,能飞了");
}
}
然后我们来看看效果吧
public class TestDuck {
public static void main(String[] args) {
Duck model = new ModelDuck();
model.display();
model.performFly();
model.performQuack();
//接下来我们给模型鸭添加火箭动力,让他能飞
model.setFlyBehavior(new FlyRocketPowered());
model.performFly();
}
}
结果如下:
我们已经深入的研究了鸭子模拟器的设计了,是时候将头探出水面呼吸空气的时候了,现在我们来看看整体的布局
上面就是重新设计后的类结构,我们期望的一切所有:鸭子继承Duck,飞行行为实现Flybehavior接口,嘎嘎叫行为实现QuackBehavior接口,多亏了这个模式,我们现在再也不需要担心遇到任何变化了。
策略模式定义:
策略模式定义了算法族,分别封装起来,让他们之间可以互相替换,此模式让算法的变化独立于使用算法的客户