我们要创建一个鸭子世界,这里的鸭子会飞还会叫(嘎嘎嘎)。当然鸭子的种类也有很多,红头的、绿头的等等。
第一个设计方案:继承
我是OO程序员,我在基类中实现fly、quack和display方法。子类继承基类的方法,这样代码还可以得到复用。看起来真是个好主意。
public class Duck { public void quack() { System.out.println("鸭子嘎嘎叫"); } public void display() { System.out.println("一只鸭子"); } public void fly() { System.out.println("鸭子在飞"); } } public class RedheadDuck extends Duck { @Override public void display() { System.out.println("一直红头鸭子"); } }
糟糕:橡胶鸭来到了鸭子世界
橡胶鸭也是鸭子,那很自然地继承鸭子类。
结果你会发现橡胶鸭也会飞?这显然是不合适的,并不是所有鸭子都会飞。你是个OO程序员,你应该想到了override,让我们试试:
//橡胶鸭继承自Duck,橡胶鸭不会飞但是会叫 public class RubberDuck extends Duck { @Override public void display() { System.out.println("一直橡胶鸭子"); } @Override public void fly() { //什么都不做 } }
第二个设计方案:用接口
看起来是个不错的设计。
绿头鸭会飞会叫所以实现了这两个接口。
橡胶鸭只会叫,所以实现了“叫”这个接口。
木制鸭既不会飞又不会叫,所以一个接口都没有实现。
代码复用率也太低了吧
我们看下Java代码,观察一下quack()方法重复了好多次。要是以后修改了quack()方法,想想你是要改多少个地方。。。
//红头鸭 public class RedheadDuck extends Duck implements Flyable, Quackable { @Override public void display() { System.out.println("一直红头鸭子"); } @Override public void quack() { System.out.println("嘎嘎叫"); } @Override public void fly() { System.out.println("飞啊飞"); } } //橡胶鸭 public class RubberDuck extends Duck implements Quackable{ @Override public void display() { System.out.println("一直橡胶鸭子"); } @Override public void quack() { System.out.println("嘎嘎叫"); } }
第三个设计方案:策略模式
这个设计一眼看去很奇怪,我们把飞和叫这种行为也定义成类了。类不应该是对现实实体的抽象吗?飞是一种动作,把它定义成类总觉得奇怪。
看这个UML图的时候请注意以下几点:
- 基类(鸭子类)是面向接口的,它的两个字段(flyBehavior和quackBehavior)的类型都是接口。
- 鸭子可能有很多种飞行的方式,“用翅膀”或者“坐火箭”。未来可能会增加其它方式,比如:用竹蜻蜓飞。策略模式可以很好地应对这种扩展。
- 以后可能要修改"用翅膀飞"这个方法,那么我们只需要修改一处代码就可以。
光看这个类图你一定还是不理解策略模式到底在做什么,在下面列出的Java代码中请注意构造函数的实现。
public class RedHeadDuck extends Duck { public RedHeadDuck() { flyBehavior = new FlyWithWing(); quackBehavior = new Quack(); } @Override public void display() { System.out.println("一直红头鸭子"); } } public class RubberDuck extends Duck { public RubberDuck() { //橡胶鸭不会飞 flyBehavior = null; //叫起来想猫一样(似乎是一家奇葩的厂商生产的橡胶鸭) quackBehavior = new QuackLikeCat(); } @Override public void display() { System.out.println("一只橡胶鸭"); } } public class Duck { protected FlyBehavior flyBehavior; protected QuackBehavior quackBehavior; public void display() { } public void performQuack() { if (flyBehavior != null) { flyBehavior.fly(); } } public void performFly() { if (quackBehavior != null) { quackBehavior.quack(); } } } public interface FlyBehavior { public void fly(); } public class FlyWithWing implements FlyBehavior { @Override public void fly() { System.out.println("两只翅膀,啪嗒啪嗒飞"); } } public class FlyWithRocket implements FlyBehavior { @Override public void fly() { System.out.println("乘坐火箭,嗖一下飞过去了"); } } public interface QuackBehavior { public void quack(); } public class QuackLikeCat implements QuackBehavior { @Override public void quack() { System.out.println("喵喵叫"); } } public class Quack implements QuackBehavior{ @Override public void quack() { System.out.println("嘎嘎叫"); } }
练习
如果你认为已经看懂了策略模式,那么试试下面的练习,会帮助你更好理解。
- 把Duck改成抽象类,并将display改成抽象方法。
创造一个会坐火箭飞的鸭子。 - 把FlyWithWing类的fly方法进行更改(改成什么随便你),留心代码复用的问题。
- 增加一个使用竹蜻蜓飞行的行为,看看是不是很容易拓展呀。
- 我们现在必须在构造函数中指定Duck的Fly和Quack行为。现在让我们在Duck中增加set方法,使之能动态改变Fly行为和Quack行为。