初探策略模式

代码链接

koonchen/design-patterns/strategy

要点

定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换。该模式使得算法可以独立于使用它的客户而变化。

假设我们现在需要对一个正文流进行分行,我们不会把算法硬编码到它们使用的类中,因为:

  • 其一这会使得客户程序变得复杂,变得庞大并且难以维护;

  • 其二不同的时候需要使用不同的算法,不使用的代码不需要写进程序里;

  • 其三当某个算法是不可分割的一部分的时候,增加新的算法或者改变已有代码是非常困难的。

我们现在可以定义一些类来封装不同的换行算法,从而避免这些问题。一个以这种方法封装的算法称为一个策略:

cover

思想很简单,实现也很简单,但组合在一起就变成了策略模式。

策略模式的适用性:

  • 许多相关的类仅仅是行为有异。策略提供了一种用多个行为中的一个行为来配置一个类的方法。
  • 需要使用一个算法的不同变体。例如,我们可能会定义一些反映不同的空间 / 时间权衡的算法。
  • 算法使用客户不应该知道的数据。使用策略模式可以避免暴露复杂的、与算法相关的数据结构。
  • 一个类定义了多种行为,并且这些行为在这个类的操作中以多个条件语句的形式出现。

策略模式的参与者:

  • Strategy — 定义所有支持的算法的公共接口。
  • ConcreteStrategy — 以 Strategy 实现的具体算法。
  • Context — 维护一个对 Strategy 对象的引用,可以定义一个接口来让 Strategy 来访问它的数据。

该模式的结构:

cover

我们通过 ContextStrategy 的相互作用以实现选定的算法,当算法被调用, Context 可以将该算法所需要的所有数据都传递给该 Strategy ,或者, Context 可以将自身作为一个参数传递给 Strategy 操作。这就让 Strategy 在需要的时候可以回调 Context

Context 将它的客户的请求转发给它的 Stratege 。客户通常创建并传递一个 ConcreteStrategy 对象给该 Context ,这样客户仅与 Context 进行交互。

策略模式的优缺点:

  • 该模式提供了相关算法系列的模式。 Strategy 类层次为 Context 定义了一系列的可供重用的算法或行为。继承有助于析取出这些算法中的公共功能。
  • 这是一个替代继承的方法。继承提供了另外一种支持多种算法或行为的方法。我们可以直接生成一个 Context 类的子类,从而给它以不同的行为。但是继承是一种硬编码行为,这将使得 Context 变得难以理解、难以维护,最后我们会得到一堆相关的类,它们唯一的差别仅仅是它们所使用的算法或行为。
  • 消除了一些条件语句。 Strategy 模式提供了用条件语句选择所需的行为以外的另一种选择。当不同的行为堆砌在一起的时候,很难避免使用条件语句来选择合适的行为。将行为封装在一个个独立的 Strategy 类中消除了这些条件语句。
  • 对实现提供选择。该模式可以提供相同行为的不同实现,客户可以根据不同时间 / 空间的抉择来选择不同的策略。
  • 缺点也有,比如客户必须了解不同的策略。客户选择一个合适的策略就必须要知道这些策略到底有何不同,此时可能不得不向客户暴露具体的实现。
  • 通信开销。无论是各个 ConcreteStrategy 实现的算法是简单还是复杂,它们都共享 Strategy 定义的接口,因此可能某些 ConcreteStrategy 不会都用到所有通过这个接口传递给它们的信息,简单的实现可能不要使用到其中的任何信息,这就意味着有些通信是浪费的,耦合度还是略高。
  • 增加了对象的数目。策略模式增加了一个应用中的对象的数目,有时候你可以将 Strategy 实现为可供各个 Context 共享的无状态的对象来减少这一开销。任何其余的状态都由 Context 来维护。因为共享的 Strategy 不应该在各名词调用之间维护状态。

实现

java 实现

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
44
45
interface Compositor {
void compose();
}

class SimpleCompositor implements Compositor {
public void compose() {
System.out.println("SimpleCompositor instance has been created");
}
}

class TeXCompositor implements Compositor {
public void compose() {
System.out.println("TeXCompositor instance has been created");
}
}

class ArrayCompositor implements Compositor {
public void compose() {
System.out.println("ArrayCompositor instance has been created");
}
}

class Composition {
private Compositor compositor;

public Composition(Compositor compositor) {
this.compositor = compositor;
}

public void repair() {
compositor.compose();
}

}

public class StrategyClient {
public static void main(String[] args) {
Composition composition = new Composition(new SimpleCompositor());
composition.repair();
composition = new Composition(new TeXCompositor());
composition.repair();
composition = new Composition(new ArrayCompositor());
composition.repair();
}
}

把专业的事情交给专业的人,可能就是策略模式想告诉我们的。

相关模式

  • Flyweight — 策略对象经常是很好的轻量级对象。

一看,确实享元模式和策略上是大同小异的,该书推荐我们用策略模式的对象到享元模式中,可以说它们是相辅相成的,享元模式就是在策略模式外面包一层对象,更好地处理策略对象。