0%

设计模式学习之---01策略模式

策略模式(strategy pattern)

1.从鸭子仿真程序开始

duck1.png

假设有一个鸭子仿真程序,Duck类是一个抽象类,定义了鸭子应该具有的行为,鸭子应该会呱呱叫,有quack()函数;所有的鸭子都会游泳,有swim()函数。这两个函数都是实函数。由于每只鸭子都有不同的表现形式,因此display函数是一个虚函数,每个鸭子的子类都需要自己实现这个函数。

2. 添加飞翔功能

这时我们需要为鸭子添加一个飞翔的功能,因此我们需要为Duck类添加一个fly()的函数。于是类的设计就变成了下面这样。

下面是Duck的实现代码:完整的代码

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

public void swim() {
System.out.println("I can swim");
}

public abstract void display();

// 这时添加了一个飞的需求
public void fly() {
System.out.println("I can fly");
}

}

这时问题出现了,并不是每一只鸭子都会飞,直接在Duck类中添加fly函数,意味着所有子类都具有了fly的能力。同样也不是每一只鸭子都会呱呱叫(例如一只玩具鸭子),因此Duck的子类必须重写这些函数以满足自己的场景。

例如下面这个例子,橡皮鸭(玩具)就需要重写quack和fly函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class RubberDuck extends Duck1 {
@Override
public void quack() {
//do nothing
}

@Override
public void fly() {
//do nothing
}

@Override
public void display() {
System.out.println("RubberDuck");
}

public static void main(String[] args) {
RubberDuck r = new RubberDuck();
r.display();
r.quack();
r.swim();
r.fly();
}
}

这样设计的问题是每出现一种新的Duck的子类都需要考虑是否重写quack和fly函数,会出现大量的重复代码

3. 一种解决方案

一种解决方案是将fly和quack从Duck超类中提取出来做成两个接口Flyable和Quackable。只有拥有这些功能的子类才会去实现这些接口。

这种解决方式破坏了代码重用的原则,每一个子类都必须实现接口中的方法,后期如果需要修改,就需要打开每一个实现了接口方法的类进行修改,后期代码维护将是一个噩梦。

4. 策略模式

定义 策略模式(strategy pattern)defines a family of algorithms,encapsulates(封装) each one, and makes them interchangeable.Strategy lets the algorithms vary independently from clients that use it.

4.1. 将变化的部分与不变的部分分离

我们知道fly和quack在不同的子类中的表现是不同的,因此将这两个方法提取出来并创建一系列的类来实现每一种行为

4.2. 面向接口编程而非面向实现

我们使用一个接口来表示一种行为,例如FlyBehavior接口表示飞翔的行为;QuackBehavior接口表示鸣叫的行为。

这时Duck的子类不负责实现fly和quack的接口,我们会实现一些列的行为类来实现这些接口。接口和类的关系如下图:

这与之前的做法是完全不同的,之前的方式是或者在Duck超类中实现一个行为或者在子类中实现一个行为。在之前的做法中我们都依赖于一种实现,我们的代码就被限定与某一种特定的实现。

在新的设计中,Duck子类将会通过接口(FlyBehavior or QuackBehavior)来使用某一个行为,这样实际行为的实现就不会被限定在Duck的子类中。

接口示例代码:完整代码

下面的代码中实现了FlyBegavior接口以及两个具体飞翔行为的实现类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public interface FlyBegavior {
public void fly();
}

class FlyWithWings implements FlyBegavior {

@Override
public void fly() {
System.out.println("I can Fly");
}
}

class FlyNoWay implements FlyBegavior {
@Override
public void fly() {
System.out.println("can't fly");
}
}

完整代码

下面的代码实现了QuackBehavior接口以及三个鸣叫的实现类

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 interface QuackBehavior {
public void quack();
}

class Quack implements QuackBehavior {
@Override
public void quack() {
System.out.println("quack");
}
}

class Squeak implements QuackBehavior {
@Override
public void quack() {
System.out.println("squeak");
}
}

class MuteQuack implements QuackBehavior {
@Override
public void quack() {
System.out.println("muteQuack");
}
}

Duck及子类示例代码:完整代码

在Duck类中定义了两个接口类型的变量,Duck并不负责实现这些接口,只是当子类创建实例时将接口的实现类作为参数传给接口变量,使得程序在执行时动态决定调用哪个具体的实现。

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
31
32
33
34
35
36
37
38
39
40
41
42
43
public abstract class Duck2 {
private FlyBegavior flyBegavior;
private QuackBehavior quackBehavior;

public Duck2(QuackBehavior quackBehavior, FlyBegavior flyBegavior) {
this.flyBegavior = flyBegavior;
this.quackBehavior = quackBehavior;
}

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

public void swim() {
System.out.println("I can siwm");
}

public abstract void display();

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


class MallardDuck2 extends Duck2 {
public MallardDuck2() {
super(new Quack(), new FlyWithWings());
}

@Override
public void display() {
System.out.println("I'm a real Mallard duck");
}

public static void main(String[] args) {
MallardDuck2 m = new MallardDuck2();
m.display();
m.performFly();
m.performQuack();
m.swim();
}
}

5. 练习题