JAVA设计模式之策略模式
开发一款游戏,里面有各种鸭子,这些鸭子有共同点:会游泳、会叫;
1.设计超类Duck,里面有swim()方法和quack()方法,所有鸭子继承此超类,那么继承的对象便都有了游泳和叫的技能;
1 public abstract class Duck { 2 /*所有的鸭子都会游泳*/ 3 public void swim() { 4 System.out.println("I can swim"); 5 } 6 /*所有的鸭子都会叫*/ 7 public void quack() { 8 System.out.println("I can quack"); 9 } 10 /*其它行为*/ 11 public abstract void display(); 12 }
2.需求变更:增加三种叫的方法,不同的鸭子叫声不同,有“吱吱叫”、“呱呱叫”,还有不会叫;那么可以覆写每个子类quack()方法,使鸭子叫声不同。
3.需求变更:鸭子得会飞;那么可以为超类Duck增加fly()方法,所有继承的子类便有了飞的技能;
4.需求变更:有些鸭子不会飞;那么覆写每个鸭子的fly()方法,使不会飞的鸭子不能飞;
这里已经存在一些问题:每当有新的鸭子出现,都需要检查fly()方法和quack()方法,确认是否需要覆写。这种继承 and 覆写的方法很糟糕。
改变:
继承或许不可取,改用接口;可以把fly和quack提取为两个接口,会飞的实现flyable接口,会叫的实现quackable接口;
改用接口带来的麻烦:每个实现flyable或quackable的子类,都需要去覆写fly()方法和quack()方法,代码根本不能复用,导致做很多重复工作。
问题分析:鸭子的fly和quack的行为在子类中不断的变化,让子类都具有这些行为是不恰当的;使用flyable和quackable接口,虽然可以解决一部分问题,但是fly()和quack()方法需在每个子类中覆写,代码不能复用,会增加很多重复工作。
有种设计原则很适合这样的情况:
一、找出需求中可能变化之处,把它们独立出来,不和不变的代码混在一起;(可以把易于变化的代码封装起来,以便可以轻易的改动和扩充此部分,而不影响不需要改动的其它部分)。
二、针对接口编程而非针对实现。
三、多用组合,少用继承。
开始将fly行为和quack行为独立出来:
准备组建两组类(完全脱离Duck),一组是fly相关的(实现FlyBehavior接口),一组是quack相关的(实现QuackBehavior接口)。有:会飞(FlyWithWings)、不会飞(FlyNoWay)、呱呱叫(Quack)、吱吱叫(Squeak)、安静(MuteQuack)等类;
1 /*会飞*/ 2 public class FlyWithWings implements FlyBehavior { 3 4 public void fly() { 5 System.out.println("I'm flying."); 6 } 7 8 } 9 /*不会飞*/ 10 public class FlyNoWay implements FlyBehavior { 11 12 public void fly() { 13 System.out.println("I can't fly."); 14 } 15 16 } 17 /*呱呱叫*/ 18 public class Quack implements QuackBehavior { 19 20 public void quack() { 21 System.out.println("呱!呱!"); 22 } 23 24 } 25 /*吱吱叫*/ 26 public class Squeak implements QuackBehavior { 27 28 public void quack() { 29 System.out.println("吱!吱!"); 30 } 31 32 } 33 /*不会叫*/ 34 public class MuteQuack implements QuackBehavior { 35 36 public void quack() { 37 System.out.println("<< Silence >>"); 38 } 39 40 }
新设计中,鸭子的子类将使用接口(FlyBehavior和QuackBehavior)所表示的行为,子类不需要去实现特定的行为(之前的设计,每个子类需要实现不同的功能覆写,把子类与实现绑定),而是由FlyBehavior和QuackBehavior的实现类来实现特定的行为。
整合鸭子的行为:
1 public abstract class Duck { 2 3 public FlyBehavior flyBehavior; 4 public QuackBehavior quackBehavior; 5 6 //可以动态的设定既成的行为 7 public void setFlyBehavior(FlyBehavior flyBehavior) { 8 this.flyBehavior = flyBehavior; 9 } 10 public void setQuackBehavior(QuackBehavior quackBehavior) { 11 this.quackBehavior = quackBehavior; 12 } 13 14 /** 15 * 其它行为 16 */ 17 public abstract void display(); 18 19 /** 20 * 能够表演飞和叫的行为 21 */ 22 public void performFly() { 23 flyBehavior.fly(); 24 } 25 public void performQuack() { 26 quackBehavior.quack(); 27 } 28 29 /** 30 * 所有鸭子都会游泳 31 */ 32 public void swim() { 33 System.out.println("I can swim."); 34 } 35 }
鸭子对象不亲自处理quack和fly的行为,而是由QuackBehavior和FlyBehavior接口的对象去处理。并且子类也可动态设定飞和叫的行为。
子类鸭子(绿头鸭)代码实例:
1 /** 2 * 绿头鸭 3 * 4 */ 5 public class MallardDuck extends Duck { 6 7 public MallardDuck() { 8 flyBehavior = new FlyWithWings(); 9 quackBehavior = new Quack(); 10 } 11 12 @Override 13 public void display() { 14 System.out.println("I'm a real mallard duck!"); 15 } 16 17 }
测试代码的行为:
1 public class DuckTest { 2 3 @Test 4 public void test() { 5 Duck mallard = new MallardDuck(); 6 mallard.swim(); 7 mallard.display(); 8 //行为没有被改变之前 9 mallard.performQuack(); 10 mallard.performFly(); 11 //行为改变之后 12 mallard.setQuackBehavior(new Squeak()); 13 mallard.setFlyBehavior(new FlyNoWay()); 14 mallard.performQuack(); 15 mallard.performFly(); 16 17 } 18 }
运行结果:
I can swim.
I'm a real mallard duck!
呱!呱!
I'm flying.
吱!吱!
I can't fly.
本例中将鸭子与两种行为QuackBehavior和FlyBehavior接口类组合使用,弹性大,且易于维护。