策略模式:定义了算法族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户端。
1. 先设计一个模拟鸭子的游戏,游戏中会出现各种鸭子,一遍游泳戏水,一边呱呱叫。
2. 现在鸭子需要添加飞的功能,我们给超类加上新的fly功能,子类继承并实现
3. 如果某一种子类不会飞,那么继承就不是合适的选择方式,如下:
、
如上,有两个问题,1. RubberDuck覆盖了quack();2. RubberDuck不会飞,缺继承了fly()。
一种简单的解决方案是,将fly()覆盖为什么也不做。但是如果又有新的子类不会叫也不会飞呢,那么是不是得继续覆盖新的子类的quack()和fly()方法。
4. 如果利用接口呢
也就是说,我们都要重复实现fly()和quack()方法,增加了大量的重复代码。
那么怎么解决问题呢?软件开发的一个不变真理就是:Change!
设计原则:找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起。
也可以这么理解:把会变化的部分取出并封装起来,以便以后可以轻易地改动或扩充此部分,而不影响不需要变化的其他部分。
记住:所有的模式都提供了一套方法:系统中某部分改变不会影响其他部分。
5. 我们分开变化和不会变化的部分
上面的分析表明,fly()和quack()是非常容易变化的类,我们将fly和quack提出来生成一系列类,如下图
6. 设计鸭子的行为
设计原则:针对接口编程(实质是针对超类型编程)
我们利用接口代表每个行为,比如说,FlyBehavior和QuackBehavior,而行为的每一个实现都将实现其中的一个接口。
所以这次鸭子类不会负责实现flying和quacking接口,反而是定义新的子类去实现FlyBehavior和QuackBehavior,新的子类称之为行为类。
7.我们用两个设计原则得到了如下的设计图
这样的设计,可以让飞行和呱呱叫的动作被其他的对象复用,因为这些行为已经与鸭子类无关了。同时我们可以新增一些行为,不会影响到既有的行为类,也不会影响“使用”到飞行行为的鸭子类。
8.最后,我们整合鸭子的行为
8.1 首先在Duck中“加入两个实例变量”,分别为flyBehavior和quackBehavior
8.2 实现performQuack();
我们调用行为对象去叫就行了。
8.3 在构造函数初始化飞行和叫的行为
问题:这里我们在构造函数对具体实现编程了,在后面的模式中,可以修正这一点。
9. 整个模型的大局观
有一个关系相当有趣:每一个鸭子都有一个FlyBehavior和一个QuackBehavior,好将飞行和叫委托给他们代为处理。
这里的关系时,我们将多个类组合起来使用。鸭子的行为不是继承来的,而是和适当的行为对象组合来的。
设计原则:多用组合,少用继承。