策略模式

鸭子游戏示例

  Joe上班的公司做了一套相当成功的模拟鸭子游戏:SimUDuck。游戏中出现各种鸭子,一边游泳戏水(swim),一边呱呱叫(quack),注意是呱呱叫,而不是吱吱叫。此系统的内部设计使用了标准的OO技术,设计了一个鸭子超类(Superclass),并让各种鸭子继承此超类。

          

 公司需求有新增,主管要求让让鸭子可以飞。

    Joe当然觉得这没有什么问题,只需要在Duck类中加入fly()方法就可以,他也是这么做的。

    在产品发布会上,主管打来电话说,他在发布会看到很多橡皮鸭子在屏幕飞来飞去……    

    Joe了忽略一件事情,并非所有的子类都可以拥有fly()方法,比如后来新加入的橡皮鸭(RubberDuck)就不能飞,而且橡皮鸭也不会呱呱叫,只能吱吱叫!在超类中加入新的行为,会使得某些并不合适该行为的子类也具有该行为。

问题:

  使用继承,父类的修改(方法的新增、修改)都会直接影响到子类。对代码所做的局部修改,影响层面可不只是局部。

 

利用继承的特性    

    Joy想,既然在父类加入方法行不通,那么可以使用继承的特性,在子类中覆盖fly()方法,让它什么都不做,同时覆盖quack()方法,让它吱吱叫。

    但,如果以后又加入新的鸭子类型,比如诱饵鸭(DecoyDuck),即不会飞也不会叫……还有很多,我们可以自己想得到。这种设计方式有一下几种缺点:

  1. 代码在多个子类中重复;(所有不会飞的鸭子,都要重新实现fly方法,造成代码重复。

  2. 运行时的行为不容易改变;

  3. 很难知道所有鸭子的全部行为;

  4. 改变会牵一发而动全身,造成其他鸭子不想要的改变;(改变了父类的某个方法,如果子类不需要进行改变,也没有进行覆盖,则会造成预期外的效果

利用接口的特性

    Joe认识到继承可能不是答案,Joe知道规格会常常改变,每当有新的鸭子子类出现,他就要被迫检查并尽可能覆盖fly()方法和quark()方法……这简直是无穷的噩梦。

    所以,他需要一个更清晰的方法,让“某些”(而不是全部)鸭子类型可以飞或叫。

    他决定把fly()方法从超类中取出来,放进一个Flyable接口中。这么依赖,只有会飞的鸭子才实现此接口。同样的方式,也可以用来设计一个Quackable接口,因为不是所有的鸭子都会叫。

    

    Joe的主管告诉他,这真是一个超笨的主意,这么一来重复的代码会变多,如果认为覆盖几个方法就算是差劲,那么对于48的Duck的子类都要稍微修改一下飞行的行为,又怎么说?

问题:

  代码重复,所有鸭子都需要去自己实现fly和quack方法。这些方法会有很多重复的,当然,也有很多不一样的。

如果你的Joe,你要怎么办?

    我们知道,并非“所有”的子类都具有飞行和呱呱叫的行为,所以继承并不是适当的解决方式。虽然Flyable与Quackable可以解决“一部分”问题(不会再有会飞的橡皮鸭),但却造成了代码无法复用,这只能算是从一个噩梦跳进另一个噩梦。身子,在会飞的鸭子中,飞行的动作可能还有多种变化……

    不管你在何处工作,构建些什么,用何种编程语言,在软件开发上,一致伴随你的哪个不变的真理就是需求变更!

    幸运的是,有一个设计原则,恰好适用于此状况。

设计原则

找出应用中可能需要变化之处,把他们独立出来,不要和哪些不需要变化的代码混在一起。

    换句话说,如果每次新的需求一来,都会使某方面的代码发生变化,那么你就可以确定,这部分的代码需要被抽出来,和其他稳定的代码有所区分。

    也就是说,把会变化的部分取出并封装起来,以便以后可以轻易地改动或拓展此部分,而不影响不需要变化的其他部分。

    好,该是把鸭子的行为从Duck类中取出来的时候了!

    我们知道Duck类内的fly()和quack()会随着鸭子的不同而改变。为了要把这两个行为从Duck类中分开,我们将把它们从Duck类中取出来,建立一组新类来代表每个行为。

    如何设计那组实现飞行和呱呱叫的行为的类呢?

    我们希望一切都有弹性,还向能够“指定”行为到鸭子的实例。避让说,产生一个新的绿头鸭实例,并指定特定类型的飞行行为给它。干脆顺便让鸭子的行为可以动态地改变好了。换句话说,应该在鸭子类中包涵设定行为的方法,这样就可以在“运行时”动态地“改变”绿头鸭的飞行行为。

    有了这些目标要实现,接着看看第二个设计原则:

设计原则

针对接口编程,而不是针对实现编程。

 

针对接口编程,其含义就是针对“超类型”编程,超类型可以是一个普通父类(?)、抽象类或者接口。 简单的理解,就是声明对象的时候,使用超类型,而不是使用具体的实现类。如下:

Dog d = new Dog();  //针对实现编程,因为声明对象使用的是具体的实现类。

d.bark;

 

Animal d = new Dog();  //针对接口编程。什么对象使用的是抽象超类或接口。

d.bark();

 

 

利用接口代表每个行为,设计两个接口,FlyBehavior与QuackBehavior,而行为的每个实现都将实现其中的一个接口。

    这样的设计,可以让飞行和呱呱叫的动作被其他的对象复用,因为这些行为已经与鸭子类无关了。来个天鹅也可以用。而我们可以新增一些行为,不会影响到既有的行为类,也不会影响“使用”到飞行行为的鸭子类。

    下面是重新设计后的类结构,所期望的一切都有:鸭子继承Duck类,飞行行为和呱呱叫行为可以在客户端调用时指派。

 

Duck类的set方法,可以使子类能运行时动态的改变行为。

   

 请特别注意类之间的关系。关系有两种,可以是IS-A(是一个)和HAS-A(有一个)或IMPLEMENTS(实现)。“有一个”可能比“是一个”更好。

    这是一个很重要的技巧。也是第三个设计原则:

设计原则

多用组合,少用继承。

 

文章参考head first一书。图文来源:https://my.oschina.net/u/2450666/blog/648789

 

posted on 2016-10-23 16:21  紫晖1989  阅读(34)  评论(0)    收藏  举报

导航