初始需求
一款模拟鸭子的游戏,涉及到如下几个类:
/**
* 鸭子抽象类
*/
public abstract class Duck {
/**
* 嘎嘎叫
*/
public void quack() {
System.out.println("嘎嘎叫");
}
/**
* 游泳
*/
public void swim() {
System.out.println("游泳");
}
/**
* 外观
*/
public abstract void display();
}
/**
* 绿头鸭
*/
public class MallardDuck extends Duck {
@Override
public void display() {
System.out.println("绿头");
}
}
/**
* 红头鸭
*/
public class RedHeadDuck extends Duck {
@Override
public void display() {
System.out.println("红头");
}
}
/**
* 橡皮鸭
*/
public class RubberDuck extends Duck {
/**
* 橡皮鸭只会吱吱叫,故重写此方法
*/
@Override
public void quack() {
System.out.println("吱吱叫");
}
@Override
public void display() {
System.out.println("橡皮");
}
}
问题出现
现在要加个需求,让绿头鸭和红头鸭会飞。直接在Duck抽象类里加fly()方法,结果问题出现了:橡皮鸭居然飞起来了。显然不合理。于是想到一个解决办法,在橡皮鸭类中覆盖fly()方法,使它不会飞。可是如果后期又增加一个“诱饵鸭”(木头做的,不会飞也不会叫),又得覆盖quack()和fly()方法。这意味着,以后每增加一个鸭子类型,都得检查并可能覆盖quack()和fly()方法。
解决办法
将变化的部分取出并封装起来,对应这个案例中的quack()和fly()方法。于是代码变成这样:
/**
* 鸭子抽象类
*/
public abstract class Duck {
protected QuackBehavior quackBehavior;
protected FlyBehavior flyBehavior;
public void setQuackBehavior(QuackBehavior quackBehavior) {
this.quackBehavior = quackBehavior;
}
public void setFlyBehavior(FlyBehavior flyBehavior) {
this.flyBehavior = flyBehavior;
}
/**
* 叫声行为
*/
public void performQuack() {
this.quackBehavior.quack();
}
/**
* 飞行行为
*/
public void performFly() {
this.flyBehavior.fly();
}
/**
* 游泳
*/
public void swim() {
System.out.println("游泳");
}
/**
* 外观
*/
public abstract void display();
}
/**
* 飞行行为接口
*/
public interface FlyBehavior {
void fly();
}
/**
* 叫声行为接口
*/
public interface QuackBehavior {
void quack();
}
public class FlyNoWay implements FlyBehavior {
@Override
public void fly() {
System.out.println("不会飞");
}
}
public class FlyWithWings implements FlyBehavior {
@Override
public void fly() {
System.out.println("用翅膀飞行");
}
}
public class Quack implements QuackBehavior {
@Override
public void quack() {
System.out.println("嘎嘎叫");
}
}
public class Squeak implements QuackBehavior {
@Override
public void quack() {
System.out.println("吱吱叫");
}
}
public class MuteQuack implements QuackBehavior {
@Override
public void quack() {
System.out.println("不会叫,是个哑巴");
}
}
/**
* 绿头鸭
*/
public class MallardDuck extends Duck {
public MallardDuck() {
this.quackBehavior = new Quack();
this.flyBehavior = new FlyWithWings();
}
@Override
public void display() {
System.out.println("绿头");
}
}
/**
* 红头鸭
*/
public class RedHeadDuck extends Duck {
public RedHeadDuck() {
this.quackBehavior = new Quack();
this.flyBehavior = new FlyWithWings();
}
@Override
public void display() {
System.out.println("红头");
}
}
/**
* 橡皮鸭
*/
public class RubberDuck extends Duck {
public RubberDuck() {
this.quackBehavior = new Squeak();
this.flyBehavior = new FlyNoWay();
}
@Override
public void display() {
System.out.println("橡皮");
}
}
应对不断变化的需求
现在,增加一个“诱饵鸭”,它不会飞也不会叫:
/**
* 诱饵鸭
*/
public class DecoyDuck extends Duck {
public DecoyDuck() {
this.quackBehavior = new MuteQuack();
this.flyBehavior = new FlyNoWay();
}
@Override
public void display() {
System.out.println("木头做的");
}
}
然后又新增一个“模型鸭”,它一开始不会飞,只会叫:
/**
* 模型鸭
*/
public class ModelDuck extends Duck {
public ModelDuck() {
this.quackBehavior = new Quack();
this.flyBehavior = new FlyNoWay();
}
@Override
public void display() {
System.out.println("我是一个玩具模型");
}
}
在游戏中,触发某个条件,比如捡到火箭助推器,模型鸭就会飞了:
public class FlyRocketPowered implements FlyBehavior {
@Override
public void fly() {
System.out.println("使用火箭助推器");
}
}
这里模拟调用的地方:
public static void main(String[] args) {
Duck duck = new ModelDuck();
duck.setFlyBehavior(new FlyRocketPowered());
duck.performFly();
duck.performQuack();
}
总结
重构后的代码可以动态的改变鸭子的飞行行为和叫声,如果把行为的实现绑死在鸭子类中,可就无法做到这样了。案例中飞行行为和叫声就是策略,不同的鸭子有不同的策略。
策略模式定义了算法族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。