设计模式(一)会飞的鸭子(策略模式)

设计原则:

多用组合(composition),少用继承。


鸭子类

首先我们有一个鸭子类

1
2
3
4
5
public abstract class duck{
quack();
swim();
display();
}

然后具体是哪种鸭子,只要去继承这个鸭子类就好了。

比如 MallardDuck 重写 display() 方法,它就是绿头鸭。

1
2
3
4
5
6
public class MallardDuck extends duck{
@Overwrite
display(){
//... green
}
}

现在,我们得让鸭子能飞

1
2
3
4
5
6
public abstract class duck{
quack();
swim();
display();
fly(); // it fly
}

然后你会惊奇地发现,橡皮鸭(RubberDuck)居然飞起来了。这不科学!

可能的解决方案

  • 重写:可以重写橡皮鸭(RubberDuck)的 fly 方法,变成什么都不做。但是这样假若我们又添加了诱饵鸭(DecoyDuck),不会 fly 也不会 quack , 我们又要去重写。显然也很麻烦。
  • 接口:可以把 fly 抽象成一个 flyable 接口,让会飞的鸭子去实现这接口。但是我们有 48 个 鸭子子类,都要去实现一遍吗?

显然,继承或重写不能解决问题,因为鸭子的行为在子类里不断地改变,并且让所有的子类都有这些行为是不切当的。 抽象出 flyable 接口 和 quackable 接口,让具备这些功能的鸭子子类去实现这些接口,要写很多重复代码,无法复用。

设计原则:

找出应用中可能需要变化的地方,把它们独立出来,不要和那些不需要变化的代码混在一起。


解决方案

既然 fly 和 quack 会随着鸭子的不同而改变,那么不妨把这两个行为抽出来,新建成 鸭子行为 类。然后在鸭子类中包含设定行为的方法。

关键点:

  • 新建鸭子行为类
  • 在鸭子类中添加设定行为的方法

这样一来,我们 new 一个 绿头鸭(MallardDuck)的时候,就能给它指定特定的 fly 行为 和 quack 行为。

设计原则:

针对接口编程,而不是针对实现编程。

具体实施

定义FlyBehavior接口,里面有一个 fly() 方法。FlyWithWings类实现了这个接口,表示用翅膀飞,FlyNoWay类也实现这个接口,表示不会飞,FlyWithRocket类也实现这个接口,表示用火箭发动机飞。

同理,定义QuackBehavior接口,然后有几个不同的实现类。比如Quack()表示呱呱叫,MuteQuack()表示不会叫。

现在,鸭子类是这样的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public abstract class duck{
QuackBehavior quackBehavior;
FlyBehavior flyBehavior;

public void performQuack(){
quackBehavior.quack();
}

public void performFly(){
flyBehavior.fly();
}

//设定鸭子的飞行行为
public void setFlyBehavior(FlyBehavior fb){
flyBehavior = fb;
}

//设定鸭子的叫声行为
public void setQuackBehavior(QuackBehavior qb){
quackBehavior = qb;
}

//... more
}

现在,橡皮鸭(RubberDuck)类看起来是这样的

1
2
3
4
5
6
7
8
public class RubberDuck extends Duck {

public RubberDuck(){
//一开始,橡皮鸭并不会飞
flyBehavior = new FlyNoWay();
QuackBehavior = new Quack();
}
}

测试类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static void main(String[] args){
// 创建一个橡皮鸭实例,此时调用橡皮鸭的构造方法
//也就是为橡皮鸭设定了不会飞,呱呱叫
Duck rubber = new RubberDuck();

// 飞一下看看(结果:不会飞)
rubber.performFly();

//设定新的飞行行为
rubber.setFlyBehavior(new FlyWithRocket());

// 再飞一下看看(结果:火箭动力飞)
rubber.performFly();
}

至此,我们的鸭子就飞起来了。

所谓组合,就是鸭子类和鸭子行为类的组合。实例化一个鸭子的时候,给它组装上对应的行为。


以上,其实就是设计模式中的 策略模式(Strategy Pattern) 了。策略模式提供了多种实体类和行为类,使用者需要哪种实体和哪种行为,可以自己去组装。


参考:

  • 《Head First 设计模式》