HeadFirst 设计模式(一)策略模式

策略模式(Strategy Pattern)

定义了算法族,分别封装起来,让它们之间可以相互替换,此模式让算法的变化独立于使用算法的客户。

设计原则

  1. 找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起
  2. 针对接口编程,而不是针对实现编程
  3. 多用组合,少用继承

模拟鸭子游戏

有一款模拟鸭子游戏,游戏中会出现各种鸭子,一边游泳,一边呱呱叫。根据面向对象技术,设计了一个鸭子的超类,让各种鸭子继承这个超类。

1
2
3
4
5
6
7
8
9
10
11
public abstract class Duck {
public void quack() {
System.out.println("呱呱叫");
}

public void swim() {
System.out.println("游泳");
}

public abstract void display();
}
1
2
3
4
5
6
7
class MallardDuck extends Duck {
@Override
public void display(){
//外观是绿头
System.out.println("外观是绿头");
}
}
1
2
3
4
5
6
7
public class RedheadDuck extends Duck {
@Override
public void display() {
//外观是红头
System.out.println("外观是红头");
}
}

测试代码:

1
2
3
4
5
6
7
8
9
10
11
12
public class Main {
public static void main(String[] args) {
MallardDuck mallardDuck = new MallardDuck();
mallardDuck.display();
mallardDuck.quack();
mallardDuck.swim();
RedheadDuck redheadDuck = new RedheadDuck();
redheadDuck.display();
redheadDuck.quack();
redheadDuck.swim();
}
}

运行结果:

外观是绿头
呱呱叫
游泳
外观是红头
呱呱叫
游泳

让鸭子会飞

某一天,增加了一个需求,需要让鸭子会飞。于是在 Duck 类中增加了一个 fly() 方法。

可怕的问题来了,某天增加了一种橡皮鸭(不会飞、会吱吱叫)、一种诱饵鸭(不会飞、不会叫)。

由于继承的原因,橡皮鸭、诱饵鸭都变成了会飞、会呱呱叫。解决办法就是在橡皮鸭、诱饵鸭类中覆盖 fly() 和 quack() 方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public abstract class Duck {
public void quack() {
System.out.println("呱呱叫");
}

public void swim() {
System.out.println("游泳");
}

public void fly() {
System.out.println("飞");
}

public abstract void display();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class RubberDuck extends Duck {
@Override
public void display() {
System.out.println("外观是橡皮鸭");
}

@Override
public void quack() {
System.out.println("吱吱叫");
}

@Override
public void fly() {
System.out.println("不会飞");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class DecoyDuck extends Duck{
@Override
public void display() {
System.out.println("外观是诱饵鸭");
}

@Override
public void quack() {
System.out.println("不会叫");
}

@Override
public void fly() {
System.out.println("不会飞");
}
}

测试代码:

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 class Main {
public static void main(String[] args) {
MallardDuck mallardDuck = new MallardDuck();
mallardDuck.display();
mallardDuck.quack();
mallardDuck.swim();
mallardDuck.fly();
RedheadDuck redheadDuck = new RedheadDuck();
redheadDuck.display();
redheadDuck.quack();
redheadDuck.swim();
redheadDuck.fly();
RubberDuck rubberDuck = new RubberDuck();
rubberDuck.display();
rubberDuck.quack();
rubberDuck.swim();
rubberDuck.fly();
DecoyDuck decoyDuck = new DecoyDuck();
decoyDuck.display();
decoyDuck.quack();
decoyDuck.swim();
decoyDuck.fly();
}
}

运行结果:

外观是绿头
呱呱叫
游泳

外观是红头
呱呱叫
游泳

外观是橡皮鸭
吱吱叫
游泳
不会飞
外观是诱饵鸭
不会叫
游泳
不会飞

利用继承来提供Duck的行为,会导致以下问题:

  1. 代码在多个子类中重复
  2. 运行时的行为不容易改变
  3. 很难知道所有鸭子的全部行为
  4. 改变会牵一发动全身,造成其他鸭子不想要的改变

利用接口实现

把 fly() 从超类中提取出来,放进一个 Flyable 接口,这么一来,只有会飞的鸭子才实现。同样也增加一个 Quackable 接口,因为有的鸭子不会叫。

1
2
3
4
5
6
7
8
public abstract class Duck {
public void swim(){
System.out.println("游泳");
}

public abstract void display();

}
1
2
3
public interface Flyable {
public void fly();
}
1
2
3
public interface Quackable {
public void quack();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class MallardDuck extends Duck implements Flyable,Quackable {
@Override
public void display() {
System.out.println("外观是绿头");
}

@Override
public void fly() {
System.out.println("飞");
}

@Override
public void quack() {
System.out.println("呱呱叫");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class RedheadDuck extends Duck implements Flyable,Quackable {
@Override
public void display() {
System.out.println("外观是红头");
}

@Override
public void fly() {
System.out.println("飞");
}

@Override
public void quack() {
System.out.println("呱呱叫");
}
}
1
2
3
4
5
6
7
8
9
10
11
public class RubberDuck extends Duck implements Quackable {
@Override
public void display() {
System.out.println("外观是橡皮鸭");
}

@Override
public void quack() {
System.out.println("吱吱叫");
}
}
1
2
3
4
5
6
public class DecoyDuck extends Duck {
@Override
public void display() {
System.out.println("外观是诱饵鸭");
}
}

测试代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/**
* 利用接口来实现,重复代码变多
*/
public class Main {
public static void main(String[] args) {
MallardDuck mallardDuck = new MallardDuck();
mallardDuck.display();
mallardDuck.quack();
mallardDuck.swim();
mallardDuck.fly();
RedheadDuck redheadDuck = new RedheadDuck();
redheadDuck.display();
redheadDuck.quack();
redheadDuck.swim();
redheadDuck.fly();
RubberDuck rubberDuck = new RubberDuck();
rubberDuck.display();
rubberDuck.quack();
rubberDuck.swim();
// 不会飞
// rubberDuck.fly();
DecoyDuck decoyDuck = new DecoyDuck();
decoyDuck.display();
// 不会叫
//decoyDuck.quack();
decoyDuck.swim();
// 不会飞
//decoyDuck.fly();
}
}

运行结果:

外观是绿头
呱呱叫
游泳

外观是红头
呱呱叫
游泳

外观是橡皮鸭
吱吱叫
游泳
外观是诱饵鸭
游泳

我们知道,并非所有的子类都具有飞行和呱呱叫的行为,所以继承并不是适当的解决方法。虽然利用 Flyable 和 Quackable 接口可以解决一部分问题,但是会造成代码无法复用,重复代码变多(假如有48种鸭子,每种鸭子都要去实现 fly() 和 quack() 方法)。

分开变化和不会变化的部分

将鸭子会变化的行为 fly 和 quack 放在分开的类中,此类专门提供某行为接口的实现。

这样的设计,飞行和呱呱叫的行为已经和鸭子无关,可以被其他对象复用。

整合鸭子的行为

在鸭子类中加入两个实例变量 flyBehavior 、quackBehavior,声明为接口类型。

每个鸭子都会动态地设置这两个变量以在运行时引用正确的行为类型(FlyWithWings 、Squeak 等)

定义行为:

1
2
3
public interface FlyBehavior {
public void fly();
}

1
2
3
4
5
6
public class FlyWithWings implements FlyBehavior {
@Override
public void fly() {
System.out.println("飞");
}
}
1
2
3
4
5
6
public class FlyNoWay implements FlyBehavior {
@Override
public void fly() {
System.out.println("不会飞");
}
}
1
2
3
public interface QuackBehavior {
public void quack();
}
1
2
3
4
5
6
public class Quack implements QuackBehavior {
@Override
public void quack() {
System.out.println("呱呱叫");
}
}
1
2
3
4
5
6
public class Squeak implements QuackBehavior {
@Override
public void quack() {
System.out.println("吱吱叫");
}
}
1
2
3
4
5
6
public class MuteQuack implements QuackBehavior {
@Override
public void quack() {
System.out.println("不会叫");
}
}

鸭子类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public abstract class Duck {

FlyBehavior flyBehavior;
QuackBehavior quackBehavior;

public abstract void display();

public void swim() {
System.out.println("游泳");
}

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

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

public void setFlyBehavior(FlyBehavior flyBehavior) {
this.flyBehavior = flyBehavior;
}

public void setQuackBehavior(QuackBehavior quackBehavior) {
this.quackBehavior = quackBehavior;
}
}

绿头鸭实现类:

1
2
3
4
5
6
7
8
9
10
11
public class MallardDuck extends Duck {
@Override
public void display() {
System.out.println("外观是绿头");
}

public MallardDuck(){
quackBehavior = new Quack();
flyBehavior = new FlyWithWings();
}
}

测试代码:

1
2
3
4
5
6
7
8
9
public class TestMallardDuck {
public static void main(String[] args) {
Duck mallard = new MallardDuck();
mallard.display();
mallard.swim();
mallard.performFly();
mallard.performQuack();
}
}

运行结果:

外观是绿头
游泳

呱呱叫

动态设定行为

创建一个模型鸭,一开始是不会飞的,后面增加一个火箭飞行的行为,让模型鸭可以飞。

1
2
3
4
5
6
7
8
9
10
11
public class ModelDuck extends Duck {
@Override
public void display() {
System.out.println("外观是模型鸭");
}

public ModelDuck(){
flyBehavior = new FlyNoWay();
quackBehavior = new Quack();
}
}
1
2
3
4
5
6
public class FlyRocketPowered implements FlyBehavior {
@Override
public void fly() {
System.out.println("火箭飞行");
}
}

测试代码:

1
2
3
4
5
6
7
8
9
10
11
12
public class TestModelDuck {
public static void main(String[] args) {
Duck model = new ModelDuck();
model.display();
model.swim();
model.performFly();
model.performQuack();
// 动态改变行为
model.setFlyBehavior(new FlyRocketPowered());
model.performFly();
}
}

运行结果:

外观是模型鸭
游泳
不会飞
呱呱叫
火箭飞行

总结

多用组合,少用继承。

使用组合建立系统具有很大的弹性,不仅可将算法族封装成类,更可以在运行时动态改变行为。