//来自《Head First 设计模式》
先从简单的模拟鸭子应用做起
Joe是一名程序员,为一家公司开发模拟鸭子游戏,该公司的主要产品是一种可以模拟展示多种会游泳和呷呷叫的鸭子的游戏。这个游戏是使用标准的面向对象技术开发的,系统里所有鸭子都继承于Duck基类,系统的核心类图如下:
Duck基类里实现quack()和swim()方法,而MallardDuck和RedheadDuck可以分别覆盖实现自己的display()方法,这样即重用了公共的部分,又支持不同子类的个性化扩展。
让鸭子飞起来
但商场上竞争激烈,董事会讨论,觉定让鸭子飞起来。
Joe研究后,发现只要在Duck里增加一个fly()方法就可以了,这样所有继承Duck的鸭子就都拥有了会飞的能力!
可怕的问题发生了……
Joe的上司:“怎么回事,我在股东会议上展示,有很多“橡皮鸭子”也在屏幕上四处乱飞,。。。。。。”
Joe 忽略了一件事: Joe在duck类中加上的飞的行为,会使子类都具有该行为。但并非duck的所有子类都会飞。Joe想到了继承
可以把橡皮鸭类中的fly()方法覆盖掉,就像覆盖quack()的做法一样……
---------------------------------------------------------------
RubberDuck
quack(){//吱吱叫}
display(){//外观是橡皮鸭}
fly(){//覆盖,变成什么事都不做}
----------------------------------------------------------
可是,如果以后加入诱饵鸭(DecoyDuck)呢?诱饵鸭是木头假鸭,不会飞也不会叫……,那不是要再重写quack()和fly()方法,以后再增加其它特殊的鸭子都要这样,这太麻烦了,而且也很混乱。
---------------------------------------------------------------
DecoyDuck //木头刻成的鸭子
quack(){//什么都不做 }
display(){//外观是诱饵鸭}
fly(){//覆盖,变成什么事都不做}
----------------------------------------------------------
Joe想到了接口
使用继承不是办法,因为董事会决定以后每6个月就会升级一次系统,所以未来的变化会很频繁,而且还不可预知。如果以后靠逐个类去判断是否重写了quack()或fly()方法来应对变化,显然混不下去!
Joe 想到了接口, 我可以把fly()从超类中取出来,放进一个“Flyable接口”中,这么一来,只有会飞的鸭子才需要实现这个接口,最好把quack()方法也拿出来放到一个接口里,因为有些鸭子是不会叫的。就像下面这样:Joe的上司知道后怒了:“这么一来重复的代码会变多!就几个鸭子还好说,但是我们有几十、上百个鸭子的时候你怎么办?难道你要重复修改上百遍吗?”
虽然使用Flyable和Quackable接口可以解决部分问题(不再有会飞的橡皮鸭子),但是却造成代码无法重用,它带来了一个维护的噩梦!甚至,在会飞的鸭子中,飞行的动作可能还是多种变化的。
分开变化和不会变化的部分
第一个设计原则:
找到系统中变化的部分,将变化的部分同其它稳定的部分隔开。
鸭子问题来说,fly()和quack()需要经常变化,其它的没有需要经常变化的地方。把fly()和quack()行为从Duck基类里隔离出来。为此需要创建两组类,一组和fly()相关,一组和quack()相关。这样鸭子的行为将被放在分开的类中,此类专门提供某行为接口的实
有了这两组行为,我们就可以组合出不同的鸭子,例如:我们可能想要实例化一个新的MallardDuck(野鸭)实例,并且给它初始化一个特殊类型的飞行行为(野鸭飞行能力比较强)。
设计鸭子的行为
我们希望一切能有弹性。比如,我们想产生一个新的绿头鸭实例,并指定特定“类型”的飞行行为给它。让鸭子的飞行行为可以动态地改变,我们将在Duck类里包含设置行为的方法。
如何实现这个目标呢?看第二个设计原则:
面向接口编程,而不要面向实现编程。
我们利用接口代表每个行为,比方说FlyBehavior与QuackBehavior,而行为的每个实现都将实现其中的一个接口。
所以这次鸭子类不负责实现Flying和Quacking接口,而是由我们制造一组其他类专门实现FlyBehavior与QuackBehavior,这就称为“行为”类。由行为类而不是Duck类来实现行为接口。
根据面向接口编程的设计原则,鸭子的子类将使用接口(FlyBehavior与QuackBehavior)表示行为,所以实际的实现不会被绑死在鸭子的子类中。也就是说,特定的具体行为编写是在FlyBehavior与QuackBehavior中实现的。
实现鸭子的行为
我们有两个接口FlyBehavior与QuackBehavior,还有它们对应的类,负责实现具体的行为:这样的设计,可以让飞行和呱呱叫的动作被其他的对象复用,因为这些行为已经与鸭子类无关了。而我们可以新增一些行为,不会影响到既有的行为类,也不会影响使用到飞行行为的鸭子类。
整合鸭子的行为
第一步:在Duck类增加两个接口类型的实例变量,分别是flyBehavior和quackBehavior,声明为接口类型,每个鸭子对象都会动态的设置这些变量(横着飞,吱吱叫,等等)。
第二步:将fly()和quack()方法从Duck类里删除,把这些行为移到FlyBehavior和QuackBehavior接口里。使用两个相似的PerformFly()和PerformQuack()方法来替换fly()和qucak()方法。
第三步:什么时候初始化flyBehavior和quackBehavior变量。最简单的办法就是在Duck类初始化的时候同时初始化他们。但是我们这里还有更好的办法,就是提供两个可以动态设置变量值的方法SetFlyBehavior()和SetQuackBehavior(),那么就可以在运行时动态改变鸭子的行为了。
看看整个设计修改后的类图:
测试Duck的代码
1、 Duck类
public abstractcalss Duck{
FlyBehavior flyBehavior;
QuackBehavior quackBehavior;
public Duck(){}
public abstract void display();
public void performFly(){ flyBehavior.fly();}
public void performQuack(){ quackBehavior.quack();}
public void swim(){ System.out.printlin(“All ducks float,evendecoys!”);}
}
2、 FlyBehavior接口
public interfaceFlyBehavior{//所有飞行行为类必须实现的接口
public void fly();
}
public classFlyWithWings implements FlyBehavior{//真飞的鸭子的飞行行为的实现
public void fly(){ System.out.pingtln(“I’m flying!!”);}
}
public classFlyNoWay implements FlyBehavior{//不会飞的鸭子
public void fly(){ System.out.pingtln(“I can’t fly!!”);}
}
3、 QuackBehavior接口
public interfaceQuackBehavior{
public void quack();//所有叫行为类必须实现的接口
}
public class Quackimplements QuackBehavior{
public void quack (){ System.out.pingtln(“Quack”);}
}
public classMuteQuack implements QuackBehavior{
public void quack (){ System.out.pingtln(“Silence”);}
}
public classSqueak implements QuackBehavior{
public void quack (){ System.out.pingtln(“Squeak”);}
}
4、 新的鸭子类型:模型鸭(ModelDuck)
public calssModelDuck extends Duck{
public ModelDuck(){
flyBehavior=new FlyNoWay();
quackBehavior=new Quack();
}
public void display(){System.out.prinlin(“I’ma model duck”);}
}
5、 测试实例
public classMiniDuckSimulator{
public static void main(){
Duck mallard=newModelDuck();
mallard.performQuack();
mallard.performFly();
}
}
6、 运行代码
动态设定行为
1、 在Duck类中,加入两个新方法:
pubic void SetFlyBehavior(FlyBehavior fb){
flyBehavior = fb;
}
public voidsetQuackBehavior(QuackBehavior qb){
quackBehavior=qb;
}
2、 建立一个新的FlyBehavior类型
public calss FlyRocketPowered implementFlyBehavior(
public void fly(){system.out.printLn(“I’mflying with a rocket”); }
}
这就是策略模式
Strategy模式官方的定义:
The Strategy Pattern defines a family of algorithms,encapsulates eachone,and makes them interchangeable. Strategy lets the algorithm varyindependently from clients that use it.(策略模式定义了一系列的算法,并将每一个算法封装起来,而且使它们还可以相互替换。策略模式让算法独立于使用它的客户而独立变化。)