Head First 设计模式学习,约定以代码开始,结束时附上定义。
Duck鸭子游戏,要设计一款游戏,初始要求游戏中有各种鸭子,一边叫一边游泳,每种鸭子外观不同。
这很容易实现,一个父类有一个会呱呱叫的方法quack(),和一个会游泳的方法swim(),还有一个抽象方法display()表示外观,不同的子类继承它,实现自己的外观就行。
//鸭子父类 public abstract class Duck { public void quack(){ System.out.println("呱呱叫"); } public void swim(){ System.out.println("游泳"); } public abstract void display(); }
//红头鸭实现 public class RedHeadDuck extends Duck{ @Override public void display() { System.out.println("红头鸭 鸭头是红色"); } }红头鸭继承父类,那么他就会叫和会游泳,自己实现了自己的外观。貌似这样不错。
接下来游戏要改,要求加入会飞的鸭子,我们能在父类中直接写么?我们应该知道不是所有的鸭子都会飞的。但貌似也可以在父类中去写,因为我们可以用子类去覆盖父类的方法。但是要考虑到如果以后有多个不会飞的鸭子加进来,那每个不会飞的鸭子都要复写父类方法,这可不是个好的现象。甚至如果有些鸭子不会叫,或者不会呱呱叫只会吱吱叫怎么办?子类都要覆盖?看来我们得想其他的方法去做这个游戏。
那么我们把会飞的方法fly(),和叫声的方法quack(),提取出来怎么样,提取两个接口Flyable和Quackable.父类Duck只有会游泳swim()方法和一个抽象的display()方法,毕竟每个鸭子都会游泳。只要是子类就继承父类,会飞的就实现Flyable接口实现fly方法,会叫怎么叫都实现Quackable接口实现quack()方法。但是要想到大部分鸭子都会叫并且都是呱呱叫,那么我们都要实现Flyable并且每个类中的方法都是一样的,重复代码变多,并且如果要改一下叫的行为,就要大量修改代码。简直是噩梦,不能复用,大量重复的代码。看来这种方法也不好。RubberDuck橡皮鸭,不会飞
是什么原因造成上面的问题呢?主要是fly与quack对于子类来说是可变的,我们无法预测每只鸭子的具体的行为是怎么样的。那怎么解决,我是不是能把fly与quack的具体的行为抽离出来分别做成一组,那么Duck,子类Duck,和具体的行为就分开了。这样之后具体行为是怎么用的呢?子类直接继承?明显不行子类还要继承父类Duck呢;父类继承?那和开始做的第一个版本有什么区别;子类持有引用?代码大量重复,每个子类都要有这两个引用;那么只能父类持有两个引用了,这也是唯一好的解决办法,但是我们又想要子类可以指定自己的行为,父类的引用肯定不能是具体类,应该是个接口,所有具体行为应该实现统一接口。
FlyBehavior和QuackBehavior为两个接口,FlyWithWings为FlyBehavior实现类,可以飞。FlyNoWay为FlyBehavior实现类。不可以飞。Quack,Squeck,NoQuack为QuackBehavior的实现类,分别表示呱呱叫,吱吱叫,什么也不做不能叫。
Duck持有两个接口的引用,并且有两个方法去调用接口的方法。当然可以动态设置,两个接口都有set方法。我们去代码实现
public interface QuackBehavior { public void quack(); }
public interface FlyBehavior { public void fly(); }
public class FlyWithWings implements FlyBehavior{ @Override public void fly() { System.out.println("fly"); } }
public class Squack implements QuackBehavior { @Override public void quack() { System.out.println("吱吱叫"); } }
public abstract class Duck { protected FlyBehavior flyBehavior; protected QuackBehavior quackBehavior; public void quack(){ quackBehavior.quack(); } public void fly(){ flyBehavior.fly(); } public void setFlyBehavior(FlyBehavior flyBehavior) { this.flyBehavior = flyBehavior; } public void setQuackBehavior(QuackBehavior quackBehavior) { this.quackBehavior = quackBehavior; } public abstract void display(); }
public class RedHeadDuck extends Duck{ public RedHeadDuck(){ flyBehavior=new FlyNoWay(); quackBehavior=new Quack(); } @Override public void display() { System.out.println("红头鸭"); } }
public class Test { public static void main(String[] args) { Duck duck=new RedHeadDuck(); duck.quack(); duck.fly(); duck.setFlyBehavior(new FlyWithWings()); duck.setQuackBehavior(new Squack()); duck.quack(); duck.fly(); } }
测试的结果是
呱呱叫 can not fly 吱吱叫 fly
可以看出中间可以动态改变鸭子的行为。这时如果再有新的鸭子加入,有不同的行为直接再创建行为类即可。甚至改变原有的行为,只要改变行为类,Duck与应用类都不需要改变。
策咯模式定义:策咯模式定义了算法族,分别封装起来,让他们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。
对照讲解解释下,算法族这里指的是行为类即fly与quack,我们针对接口分别封装起来,每个行为封装为一个类。可以互相替换即面向接口变成,具有set方法,可以动态变化行为。最后真的客户完全不关心你算法的实现,和自己一点关系没有。