设计模式–策略模式

来源:互联网 发布:人生 不 快乐 知乎 编辑:程序博客网 时间:2024/06/05 06:28

设计模式–策略模式

@(设计模式)[策略模式|读书笔记|Head First设计模式]

策略模式:该模式定义了算法族,让算法和对象分开,使得算法可以独立于使用它的对象而变化。

例子

设计一套成功的模拟鸭子游戏:游戏中会出现各种鸭子,一边游泳戏水,一边呱呱叫。
Alt text
如上图所示,我们定义一个Duck父类,实现其中quack()和swim()方法,并定义一个抽象的方法display(); 具体的子类继承父类Duck的quack()和swim()方法,并实现其特定的display()方法。
新需求:现在我们需要根据鸭子的不同加上会飞的功能。
Alt text
如上图所示,在父类Duck中增加一个新方法,会使得某些并不适合该行为的子类也具有该行为。( 对代码做局部修改,影响的层面不只是局部。(如例子中会导致会飞的橡皮鸭))所以我们在其特定的子类中必须覆盖重写其方法,来体现出该鸭子的特殊性。(橡皮鸭。)
如果又来一种新种类的鸭子(诱饵鸭–既不会飞也不会叫)我们又要在其子类中重写其各种方法。

利用继承的方式来提供Duck的行为,会导致:
1.代码在多个子类中重复。
2.在父类中的改变会牵一发而动全身,造成其他子类的鸭子不想要的改变。
3.运行时的行为不容易改变。
4.很难知道所有鸭子的全部行为。

这时大家可能会想到利用接口来辅助
把fly()和quack()从父类中抽取出来,定义在接口中,每个具体的子类来实现其接口方法。
Alt text
虽然利用接口可以解决问题。但是这么一来重复的代码会变得更多,并且代码无法复用。

总结问题

我们已经知道使用继承并不能很好地解决问题,鸭子的行为在子类是不断变化的,如果修改父类的方法(给鸭子增加一个新的行为)就会导致所有的子类都具有该行为,必须在子类中重写这些方法,这显然不能很好的解决问题。 将新的行为接口化,看起来能解决问题(例如:只有会飞的鸭子才能继承Flyable),但是Java接口中的方法没有具体实现的代码,继承接口无法达到代码复用。这意味着:无论何时你需要修改某个行为,都必须追中到每一个继承此接口的类中修改它,一不小心,可能造成新的错误。
一个好的设计原则应该找出应用中可能需要变化的地方,把这些方法“封装”起来,不要和那些不需要变化的代码混在一起,好让其他部分不受到它的影响。

解决方法:
1.对于Duck类,分开变化和不会变化的部分。

我们知道Duck类的fly()和quack()会随着鸭子的不同而改变。为了把这两个行为从Duck类中分开,我们将其取出,建立一组新类来代表每个行为。
设计原则:针对接口编程,而不是针对实现编程。
鸭子的行为被放在分开的类中,此类专门提供某行为接口的实现。这样,Duck类就不需要知道行为的实现细节。
下图为我们抽取出Duck类的fly()方法。将其定义为接口,并且有多种具体实现该接口的类。
Alt text
在这种新的设计方法中,鸭子的子类将使用接口所表现出来的行为(FlyWithWings和FlyNoWay),实际的“实现过程”不会绑死在鸭子的子类中。
抽取Duck类中的quack()方法与之类似这里不再赘述。

2.整合鸭子的行为。

如下图所示,在Duck类中“加入两个实例变量”,声明为接口类型(FlyBehavior和QuackBehavior),每个鸭子对象都会动态的设置这些变量以在运行时引用正确的行为。(例如:FlyWithWings、FlyNoWay)
Alt text
现在我们来实现performQuack()

public class Duck{    QuackBehavior quackBehavior;//接口类型    FlyBehavior flyBehavior;    ...省略其他    //具体实现    public void performQuack(){        quackBehavior.quack();   //委托给具体行为类    }    public void performFly(){        flyBehavior.fly(); //委托给具体行为类    }    ...省略其他}

对于具体子类例如:MallardDuck类。

public class MallardDuck extends Duck{    public MallardDuck(){    ...省略其他,以fly()为例子        flyBehavior =new FlyWithWings();            }            //继承父类方法    public void display(){        System.out.println("I'am a real Mallard duck");         }}

当绿头鸭MallardDuck实例化时,它的构造器会把继承来的flyBehavior实例变量初始化成FlyWithWings类型的实例(FlyWithWings是FlyBehavior的具体实现类),同样的处理方式可以用到其他行为上,这里我们省略,只以fly()为例。
目前的做法已经可以基本实现问题要求,接下来我们继续优化:在鸭子子类中通过“设定方法”(setter method)来设定鸭子行为,而不是在鸭子的构造器中实例化。

Duck类

public class Duck{    QuackBehavior quackBehavior;//接口类型    FlyBehavior flyBehavior;    ...省略其他    //具体实现    public void performQuack(){        quackBehavior.quack();   //委托给具体行为类    }    public void performFly(){        flyBehavior.fly(); //委托给具体行为类    }    ...省略其他    public void setFlyBehavior(FlyBehavior fb){    flyBehavior=fb;    }    public void setQuackBehavior(QuackBehavior qb){    quackBehavior=qb;    }       }

FlyBehavior接口类和其具体行为实现类

//接口类public interface FlyBehavior{    public void fly();     }//具体行为实现类public class FlyWithWings implments FlyBehavior{    public void fly(){        System.out.println("I'am flying!!");    }}//具体行为实现类public class FlyRocketPowered implments FlyBehavior{    public void fly(){        System.out.println("I'am  flying with a rocket!!");    }}//具体行为实现类public class FlyNoWay implments FlyBehavior{    public void fly(){        System.out.println("I can't fly!!");    }}

同理QuackBehavior接口类和其具体行为实现类

//接口类public interface QuackBehavior{    public void quack();     }//具体行为实现类public class Quack implments FlyBehavior{    public void quack(){        System.out.println("Quack");    }}//具体行为实现类public class MuteQuack implments FlyBehavior{    public void quack(){        System.out.println("I can't quack!!");    }}

具体的子类这里我们列出一个(ModelDuck)

//接口类public class ModelDuck extends Duck{    public ModelDuck(){        flyBehavior =new FlyNoWay();        quackBehavior=new Quack();    }    public void display(){        System.out.println("I'am a model duck");    }}

测试程序类DuckSimulator.java

//接口类public class DuckSimulator{    public static void main(String[] args){        Duck model =new ModelDuck();        model.performFly();        model.setFlyBehavior(new FlyRocketPowered());        model.performFly();    }}

这样优化,就可以动态的改变子类鸭子的飞行行为。

Alt text

至此,鸭子模拟器的设计已经完成,如上图所示,我们来总结下封装行为的大局观.

鸭子的子类继承Duck类,飞行行为实现FlyBehavior接口,叫声行为实现QuackBehavior接口。并且定义了setFlayBehavior()和setQuackBheavior()方法,可以动态改变鸭子的行为。这就是应用策略模式的好处。将鸭子的“一组行为”(一族算法)封装起来,可以针对不同的鸭子定义不同的行为(算法),并且行为的变化(算法的变化)也独立于具体的鸭子。

设计原则:多用组合,少用继承。针对接口编程,而不是针对实现编程。

使用组合建立系统具有很大的弹性,不仅可以将算法族封装成类,更可以“在运行时动态地改变行为”,只要组合的行为对象符合正确的接口标准即可。