由一个场景来引出问题
场景
有一天,公司要做一个模拟鸭子游戏,游戏里有各种鸭子,会游泳,会叫。你会怎么设计?
最初的设计
设计一个鸭子的超类(AbstractDuck),并让各个鸭子子类继承。
abstract class AbstractDuck {
void quack() {
System.out.println("呱呱叫");
}
void swim() {
System.out.println("游泳");
}
/**
* 由于每一种鸭子外观不同,所以该方法是抽象的
*/
abstract void display();
}
场景
当游戏上线后,市场竞争加剧,需要迭代更新游戏,这时产品说要让鸭子能飞,你会怎样设计?
第1次修改尝试
在AbstractDuck中加上fly()方法。
void fly() {
System.out.println("飞");
}
出现问题
所有的鸭子都能飞了,连橡皮鸭子都能飞了,可怕......
第2次修改尝试
橡皮鸭不会呱呱叫,所以把quack()方法覆盖成吱吱叫。但是“飞”这里怎么处理?像覆盖“呱呱叫”方法一样覆盖掉?但是如果以后加入诱饵鸭怎么办?诱饵鸭是木头的假鸭,不会飞也不会叫,难道要把“飞”和“叫”方法都覆盖成什么也不做?
思考
继承的方式是行不通的,因为如果产品说以后每个月都要更新产品,那么每次新的鸭子出现,都要被迫检查并可能需要覆盖“飞”和“叫”方法,做这么多无用功,很蛋疼。那么如果把某些功能变成接口呢?比如把“飞”从超类中抽出来,放进一个“Flyable”接口中,只有会飞的鸭子才实现此接口。同样,其他的功能也如此处理。但是这样也有一个问题,很多种鸭子都会飞,每次都要实现这个接口中的“飞”方法,写重复的代码,累死人,怎么办?设计模式的思想是分离易变和不变部分,这里可以把鸭子的行为从Duck类中抽出来。
分离变化和不会变化的部分
从哪里开始呢?目前来看,只有“飞”和“叫”有点问题,其它地方暂时没啥问题,依照“不过早优化”原则,没出问题的就先不动它。
第3次修改尝试
这里又有另一种思想“针对接口编程”,这样才能抽象化。其实还是把“飞”和“叫”抽成接口,但是此时不是鸭子的子类去实现这两个接口了,而是每个接口都有自己的一组实现类,创建鸭子子类的时候在指定具体的“飞”和“叫”的实现类。这样行为成为一套独立的体系,鸭子成为一套独立的体系。只有用到的时候再从两个体系中取具体实现组合起来。
// 新的鸭子超类
abstract class AbstractDuck {
QuackBehavior quackBehavior;
FlyBehavior flyBehavior;
public void setQuackBehavior(QuackBehavior quackBehavior) {
this.quackBehavior = quackBehavior;
}
public void setFlyBehavior(FlyBehavior flyBehavior) {
this.flyBehavior = flyBehavior;
}
void swim() {
System.out.println("游泳");
}
void fly() {
flyBehavior.fly();
}
void quack() {
quackBehavior.quack();
}
/**
* 外观:由于每一种鸭子外观不同,所以该方法是抽象的
*/
abstract void display();
}
飞的接口和实现类
public interface FlyBehavior {
void fly();
}
public class FlyNoWay implements FlyBehavior {
public void fly() {
System.out.println("不会飞");
}
}
public class FlyWithWings implements FlyBehavior {
public void fly() {
System.out.println("飞");
}
}
叫的接口和实现类
public interface QuackBehavior {
void quack();
}
public class Quack implements QuackBehavior {
public void quack() {
System.out.println("呱呱叫");
}
}
public class Squeak implements QuackBehavior {
public void quack() {
System.out.println("吱吱叫");
}
}
public class MuteQuack implements QuackBehavior {
public void quack() {
System.out.println("不会叫,叫不出声");
}
}
使用
public class MallardDuck extends AbstractDuck {
public MallardDuck(QuackBehavior quackBehavior, FlyBehavior flyBehavior) {
super.quackBehavior = quackBehavior;
super.flyBehavior = flyBehavior;
}
@Override
void display() {
System.out.println("绿头鸭");
}
}
测试
public class StrategyTest {
public static void main(String[] args) {
MallardDuck mallardDuck = new MallardDuck(new Quack(), new FlyWithWings());
mallardDuck.display();
mallardDuck.swim();
mallardDuck.quack();
//中途可以灵活改变叫声
mallardDuck.setQuackBehavior(new Squeak());
mallardDuck.quack();
mallardDuck.fly();
}
}
//绿头鸭
//游泳
//呱呱叫
//吱吱叫
//飞
这里如果不再把鸭子的行为说成是“一组行为”,而是想成“一族算法”。特别注意类之间的“关系”,AbstractDuck和飞行为以及叫行为之间的关系是HAS-A(有一个)。
策略模式(Strategy Pattern)定义了算法族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。