策略模式——从鸭子入手
一个鸭子类,子类有绿头鸭和红头鸭。
每个鸭子都会叫,也会游泳,所以由超类来处理这些共同的东西。而display()方法是用来显示,更加直观的体现各部分的关系。
当涉及维护时,为了复用而使用继承,问题往往更大。
如果我们在Duck类中加一个fly()飞的方法,结果会怎么样?
由于考虑不周,在Duck类中增加了fly(),这样ToyDuck玩具鸭子也继承了这个方法。可是事实上玩具鸭子是飞不起来的,那怎么办?类似的问题是,玩具鸭子只会吱吱地响,而不会向红头鸭那样呱呱的叫。
这时,我们就要用“覆盖”了,将ToyDuck玩具鸭中的fly()方法覆盖掉,变成什么也不做,类似的,将quack()方法覆盖为吱吱叫。
到这里,好像勉强是可以完成任务了。但当一个新的鸭子子类出现的话,我们都要考虑它是不是能飞,是不是能叫,是怎样飞的,怎样叫的。可见,这样的实现方法并不能一劳永逸。
这时,我们可以考虑将一些容易或可能经常发生变化的一些方法,如fly,quack,从Duck抽象类中取出来,放到一个接口中,然后让会飞的绿头鸭、红头鸭去实现这个接口,而玩具鸭子就不去实现它了。
引入接口之后,事情就方便多了,可这样一来,各个子类的代码就没办法复用了。
找出可能变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起。
为了分开变化的和不变化的部分,需要建立两组类,一个是与fly相关的,一个是quack相关的,每组类各自实现各自的动作。我们将这两个行为从Duck类中分开,建立一组新类。
针对接口编程,而不是针对实现。
鸭子的子类将使用接口FlyBehavior和QuackBehavior所表示的行为,事实上的实现不会被绑死在鸭子的子类中。比如,玩具鸭子不能飞,这样的设计就可以在运行时动态的改变它的飞行行为。
整合之后的全图就变成了下面的样子:
由于精力有限,Duck中的部分地方没有说明。比如Duck类加了中两个实例变量 fly 和 quack,但并不影响理解。
对比之前商场打折的例子:
通过这两个例子,相信你会对策略模式有了更深入的理解。即使同样是理解了,总结与不总结收获差别蛮大的。
顺着书上的思路走,虽然挺麻烦的,也走了弯路,但正是这样,才能更深刻的理解那些原则、经验。剩下要做的就是结合代码再重新梳理一下思路了。