《设计模式》— 行为型模式 — 策略模式
一、动机
有许多算法可以对一个文本流进行分行。将这些算法硬编进使用它们的类中是不可取的。其原因如下:
- 需要换行功能的客户程序如果直接包含换行算法代码的话将会变得复杂,这使得客户程序庞大并且难以维护,尤其当其都需要支持多种换行算法时问题会更加严重。
- 不同的时候需要不同的算法,我们不想支持我们并不使用的换行算法。
- 当换行功能时客户程序的一个难以分割的成分时,增加新的换行算法或改变现有换行算法将十分困难。
我们可以定义一些类来封装不同的换行算法,从而避免这些问题。一个以这种方法封装的算法称为策略:
Composition 维护对 Compositor 对象的引用。当其收到重新格式化的请求时,会将该操作委托给 Compositor 对象。该策略实例由客户指定和装载。
二、适用性
- 许多相关的类仅仅是行为有异。策略提供了一种用多个行为中的一个行为来配置一个类的方法。
- 需要使用一个算法的不同变体。例如,我们可能会使用不同空间时间权衡的算法。如果这些算法是动态可配置的,则可以使用策略模式。
- 算法使用客户不应该知道的数据。可以使用策略模式以避免暴露复杂的、与算法相关的数据结构。
- 一个类定义了多种行为,并且这些行为在这个类的操作中以多个条件语句的形式出现。
三、结构
四、参与者
1、Strategy
提供支持的算法的公有接口。
2、ConcreteStrategy
某具体算法的实现
3、Context
- 用一个 ConcreteStrategy 对象来配置
- 维护一个对 Strategy 对象的引用
- 可定义一个接口来让 Strategy 访问它的数据
五、协作
- Strategy 和 Context 相互作用以实现选定的算法。当算法被调用时, Context 可以将该算法所需要的所有数据都传递给该 Strategy。或者,Context 可以将自身作为一个参数传递给 Strategy 操作。这就让 Strategy 在需要时可以回调 Context。
- Context 将客户的请求转发给它的 Strategy。客户通常创建并传递一个 ConcreteStrategy 对象给该 Context,这样,客户仅与 Context 交互。通常由一系列的 ConcreteStrategy 类可供客户从中选择。
六、效果
1、一个替代继承的方法
我们可以使用模板方法模式,借由继承实现相同的功能。然而,这会将算法行为硬编码到 Context 中。尤其是算法并不属于 Context 的一部分,这就违反了单一职责原则。将算法封装在独立的 Strategy 类中使得我们可以独立于其 Context 改变它。
2、客户必须了解不同的策略
本模式中有一个潜在的缺点,就是需要用户选择一个合适的策略。那么用户就必须要知道不同策略之间的区别。此时可能不得不向客户暴露具体的实现问题。因此仅当这些不同行为的变体与客户相关时,才使用策略模式。
3、增加了对象的数目
有时我们可以将策略模式实现为可供各 Context 共享的无状态的对象来减少这一开销。任何其余的状态都由 Context 维护。Context 在每一次对 Strategy 对象的请求中都将这个状态传递出去。共享的 Strategy 不应在各次调用之间维护状态。这可以借助享元模式实现。
七、实现
1、定义 Strategy 和 Context 接口
Strategy 和 Context 接口必须使得 ConcreteStrategy 能够有效地访问它所需要的 Context 中的任何数据,反之亦然。一种办法是让 Context 将数据放在参数中传递给 Strategy。这使得二者解耦,但是可能会导致数据的冗余。
另一种办法是让 Context 将自身作为一个参数传递给 Strategy,该 Strategy 再显式地向 Context 请求数据。但这种情况要求 Context 必须定义精细的数据接口,增加了 Context 和 Strategy 的耦合性。
2、将 Strategy 作为模板参数
在C++中,可以利用模板机制用一个 Strategy 配置一个类。需要注意的是,模板属于编译时绑定,因此不适用于需要动态修改算法的情况。
八、应用
在 Qt 中,窗体支持通过 setLayout 方法设置布局对象。用户可以使用系统预定义的布局类,也可以自定义布局类并实例化它们。这样可以动态的实现同一窗体不同布局方式的控制。
一个更简单的例子是在 C++ 的 STL 中,算法库大多支持设置判别式以替换预定义的操作符。这样可以使用用户指定的函数(策略)执行算法。