设计模式之策略模式

设计模式之策略模式

什么是策略模式 ?

  • 定义一系列算法,封装每个算法,并使它们可互换。策略模式使算法独立于使用该算法的客户端而变化。

先简单的模拟鸭子的应用做起

系统的内部设计使用标准的OOP技术,设计了一个鸭子的超类(Superclass),并让各种鸭子继承此超类。

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
/**
* 鸭子超类
*/
public abstract class AbstractDuck {

/**
* 嘎嘎叫
*/
protected void quack() {
System.out.println("嘎嘎叫...");
}

/**
* 游泳
*/
protected void swim() {
System.out.println("游泳...");
}

/**
* 由于每种鸭子外观不同,提供抽象方法
*/
protected abstract void display();

// other method...
}
1
2
3
4
5
6
7
8
9
/**
* 绿头鸭
*/
public class MallardDuck extends AbstractDuck {
@Override
protected void display() {
System.out.println("头是绿色的.");
}
}
1
2
3
4
5
6
7
8
9
/**
* 红头鸭
*/
public class RedheadDuck extends AbstractDuck {
@Override
protected void display() {
System.out.println("头是红色的.");
}
}

后来新增了需求,让鸭子飞起来。于是在Duck超类加上fly()方法,然后所有的鸭子继承fly()。

1
2
3
4
5
6
7
8
9
10
/**
* 鸭子超类
*/
public abstract class AbstractDuck {
// other method ...

protected void fly() {
System.out.println("鸭子飞起来...");
}
}

但是,可怕的问题发生了….

由于并发所有的Duck类都会飞,在超类加上新的行为,会使得某些并不合适该行为的子类也具有该行为。当涉及维护时,为了复用目的而使用继承并不完美。

利用接口改造

可以把fly()从超类中提取出来,放进“Flyable接口”中,这么一来,只有会飞的鸭子才实行此接口。同样方式,也可以用设计一个“Quackable接口”,因为不是所有的鸭子都会叫。

1
2
3
4
5
6
/**
* 飞行行为
*/
public interface Flyable {
void fly();
}
1
2
3
4
5
6
/**
* 叫声行为
*/
public interface Quackable{
void quack();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* 鸭子超类
*/
public abstract class AbstractDuck {
/**
* 游泳
*/
protected void swim() {
System.out.println("游泳...");
}

/**
* 由于每种鸭子外观不同,提供抽象方法
*/
protected abstract void display();

// other method ...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* 绿头鸭,既会飞,又会发出叫声
*/
public class MallardDuck extends AbstractDuck implements Flyable, Quackable {
@Override
protected 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
/**
* 红头鸭,既会飞,又会发出叫声
*/
public class RedheadDuck extends AbstractDuck implements Flyable, Quackable {
@Override
protected 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
/**
* 橡皮鸭,不会飞,只会发出叫声
*/
public class RubberDuck extends AbstractDuck implements Quackable {
@Override
protected void display() {
System.out.println("橡皮鸭");
}

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

1
2
3
4
5
6
7
8
9
/**
* 诱饵鸭,既不会飞,也不会叫
*/
public class DecoyDuck extends AbstractDuck {
@Override
protected void display() {
System.out.println("诱饵鸭五颜六色");
}
}

我们知道,并非所有的子类都具有飞行和呱呱叫的行为,所以“继承”并不是适当的解决方式。虽然Flyable和Quackable可以解决一部分问题,但是造成代码无法复用,而且对于很多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
/**
* 鸭子超类
*/
public abstract class AbstractDuck {

/**
* 飞行行为
*/
protected FlyBehavior flyBehavior;

/**
* 叫声行为
*/
protected QuackBehavior quackBehavior;

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

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

/**
* 游泳
*/
protected void swim() {
System.out.println("All ducks float, even decoys...");
}

/**
* 由于每种鸭子外观不同,提供抽象方法
*/
protected abstract void display();

// other method...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* 飞行行为
*/
public interface FlyBehavior {
void fly();
}

public class FlyWithWings implements FlyBehavior {
@Override
public void fly() {
System.out.println("I'm flying...");
}
}

public class FlyNoWay implements FlyBehavior {
@Override
public void fly() {
// 什么都不做,不会飞
System.out.println("I can't 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
25
26
27
28
/**
* 叫声行为
*/
public interface QuackBehavior {
void quack();
}

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

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

public class MuteQuack implements QuackBehavior {
@Override
public void quack() {
// 什么都不做,不会叫
System.out.println("silence");
}
}
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
public class MallardDuck extends AbstractDuck {

public MallardDuck() {
// 嘎嘎叫
super.quackBehavior = new Quack();
// 会飞行
super.flyBehavior = new FlyWithWings();
}

@Override
protected void display() {
System.out.println("I'm a green head duck.");
}
}

public class ModelDuck extends AbstractDuck {

public ModelDuck() {
// 设置模型鸭的行为,不会飞的行为
super.flyBehavior = new FlyNoWay();
super.quackBehavior = new Quack();
}

@Override
protected void display() {
System.out.println("I'm a model duck.");
}
}

动态设定行为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* 鸭子超类
*/
public abstract class AbstractDuck {

// 其他实例...

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

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

// other method...
}

运行结果

1
2
3
4
5
6
7
8
9
public class MiniDuckSimulator {
public static void main(String[] args) {
AbstractDuck duck = new MallardDuck();
duck.performFly();
// 动态的改变行为
duck.setQuackBehavior(new Squeak());
duck.performQuack();
}
}

在运行时改变行为,只需要调用父类的setter方法就可以了。我们通过策略模式来应对任何的改变,使得程序变得更简易健壮。

设计原则

多用组合,少用继承。

总结

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