1.意图
定义一系列的算法,把它们一个个封装起来,并且使它们可相互替换。本模式使得算法可独立于使用它的客户而变化。
2.别名
政策(Policy)
3.动机
有许多算法可以对一个正文流进行分行。将这些算法硬编进使用它们的类中是不可取的,其原因如下:
- 需要换行功能的客户程序如果直接包含换行算法代码的话将会变得复杂,这使得客户程序庞大并且难以维护,尤其当其需要支持多种换行算法时间问题会更加严重。
- 不同的时候需要不同的算法,我们不想支持我们并不使用的换行算法。
- 当换行功能是客户程序的一个难以分割的成分时,增加新的换行算法或改变现有算法将十分困难。
我们可以定义一些类来封装不同的换行算法,从而避免这些问题。一个以这种方法封装的算法称为一个策略(strategy),如下图所示。
假设一个Composition类负责维护和更新一个正文浏览器程序中显示的正文换行。换行策略不是Composition类实现的,而是由抽象的Compositor类的子类各自独立地实现的。Compositior各个子类实现不同的换行策略:
- SimpleCompositor实现一个简单的策略,它一次决定一个换行位置。
- TeXCompositor实现查找换行位置的TEX算法。这个策略尽量全局地优化换行,也就是一次处理一段文字的换行。
- ArrayCompositor实现一个策略,该策略使得每一行都含有一个固定数目的项。例如,用于对一系列的图标进行分行。
Composition维护对Compositor对象的一个引用。一旦Composition重新格式化它的正文,它就将这个职责转发给它的Compositior对象。Composition的客户指定应该使用哪一种Compositor的方式是直接将它想要的Compositor装入Composition中。
4. 适用性
当存在以下情况时使用Strategy模式
- 许多相关的类仅仅是行为有异。“策略”提供了一种用多个行为中的一个行为来配置一个类的方法。
- 需要使用一个算法的不同变体。例如,可能会定义一些反映不同的空间、时间权衡的算法。当这些变体实现为一个算法的类层次时,可以使用策略模式。
- 算法使用客户不应该知道的数据。可使用策略模式以避免暴露复杂的、与算法相关的数据结构。
- 一个类定义了多种行为,并且这些行为在这个类的操作中以多个条件语句的形式出现。将相关的条件分支移入它们各自的Strategy类中以代替这些条件语句。
5.结构
6、参与者
- Strategy(策略,如compositor)
定义所有支持的算法的公共接口。Context使用这个接口来调用某ConcreteStrategy定义的算法。
- ConcreteStategy(具体策略,如SimpleCompositor, TeXComositor, ArrayCompositor)
以Strategy接口实现某具体算法
- Context(上下文,如Composition)
- 用一个ConcreteStrategy对象来配置
- 维护一个对Strategy对象的引用
- 可定义一个接口来让Strategy访问它的数据。
7.协作
- Strategy和Context相互作用以实现选定的算法。当算法被调用时,Context可以将该算法所需要的所有数据都传递给该Strategy。或者,Context可以将自身作为一个参数传递给Strategy操作。这就让Strategy在需要时可以回调Context。
- Context将它的客户的请求转发给它的Strategy。客户通常创建并传递一个ContextStrategy对象给该Context;这样,客户仅与Context交互。通常有一系列的ConcreteStrategy类可供客户从中选择。
8. 效果
Strategy模式有下面的一些优点和缺点:
- 相关算法系列 Strategy类层次为Context定义了一系列的可供重用的算法或行为。继承有助于析取出这些算法中的公共功能。
- 一个替代继承的方法 继承提供了另一种支持多种算法或行为的方法。你可以直接生成一个Context的子类,从而给它以不同的行为。但这会将行为硬行编码到Context中,而将算法的实现与Context的实现混合起来,从而使Context难以理解、难以维护和难以扩展,而且还不能动态地改变算法。
- 消除了一些条件语句 Strategy模式提供了用条件语句选择所需的行为以外的另一种选择。当不同的行为堆砌在一个类中,很难避免使用条件语句来选择合适的行为。将行为封装在一个个独立的Strategy类中消除了这些条件语句。
例如,不用Strategy,正文换行的代码可能如下,
Void Composition::Repair() {
Switch (_breakingStrategy) {
Case SimpleStratey:
ComposeWithSimplecompositor();
Break;
Case TexStrategy:
ComposeWithTexCompositor();
Break;
// …
}
// merge results with existing composition, if necessary.
}
Strategy模式将换行的任务委托给一个Strategy对象从而消除了这些case语句:
Void Composition::Repair() {
_compositor->Compose();
// merge results with existing composition, if necessary
}
含有许多条件语句的代码通常意味着使用Stragtegy模式。
- 实现的选择
Strategy模式可以提供相同行为的不同实现。
- 客户必须了解不同的Strategy
本模式有一个潜在的缺点,就是一个客户要选择一个合适的Strategy就必须知道这些Strategy到底有何不同。此时可能不得不向客户暴露具体的实现问题。因此仅当这些不同行为变体与客户相关的行为时,才需要使用Strategy模式。
- Strategy和Context之间的通信开销
无论各个ConcreteStrategy实现的算法是简单还是复杂,它们都共享Strategy定义的接口。因此很可能某些ConcreteStrategy不会用到所有通过这个接口传给它们的信息;简单的ConcreteStrategy可能不使用任何参数。
- 增加了对象的数目 Strategy
Strategy增加了一个应用中的对象的数目。有时你可以将Strategy实现为可供Context共享的无状态的对象来减少这一开销。
9. 实现
考虑下面的实现问题:
- 定义Strategy和Context接口
Strategy和Context接口必须使得ConcreteStrategy能够有效的访问它所需要的Context中的数据,反之亦然。一种办法是让Context将数据放在参数中传递给Strategy操作,也就是说,将数据发送给Stragtegy。这使得Strategy和Context解耦。但另一个方面,Context可能发送一些Strategy不需要的数据。
另一种办法是让Context将自身作为一个参数传递给Strategy,该Strategy再显式地向该Context请求数据。或者,Strategy可以存储对它的Context的一个引用,这样根本不再需要传递任何东西。
- 将Strategy作为模板参数
在c++中可利用模板机制用一个Strategy来配置一个类。然而这种技术仅当下面条件满足时才可以使用:(a)可以在编译时选择Strategy (b)它不需在运行时改变。在这种情况下,要被配置的类被定义为一个Strategy类作为一个参数的模板类:
Template <class AStrategy>
Calss Context {
Void Operation() { theStrategy.DoAlgorith();}
// ..
Private:
AStrategy theStrategy;
};
当它被实例化时该类用一个Strategy类配置:
Class MyStrategy {
Public:
Void DoAlgorithm();
};
Context<MyStrategy> aContext;
使用模板不再需要定义给Strategy定义接口的抽象类。把Strategy作为一个模板参数也使得可以将一个Strategy和它的Context绑定在一起,从而提供效率。
- 使Strategy对象成为可选的
如果即使不使用额外的Strategy对象的情况下,Context也还有意义的话,那么它还可以被简化。Context在访问其Strategy前先检查它是否存在,如果有就使用它;如果没有,那么Context执行缺省行为。这种方法的好处是客户根本不需要处理Strategy对象,除非它们不喜欢缺省的行为。
10. 代码示例
这里给出动机一节例子中的高层代码
Composition类维护一个Component实例的集合,它代表了一个文档中的正文和图形元素。Composition使用一个封装了某种分行策略的Compositor子类实例将Component对象编排成行。每一个Component都有相应的正常大小、可伸展性、和可收缩性。可伸展性定义了该Component可以增长到超出正常大小的程度;可收缩性定义了它可以收缩的程度。Composition将这些值传递给一个Compositor,它使用这些值来决定换行的最佳位置。
Class Composition {
Public:
Composition(Compositor*);
Void Repair();
Private:
Compositor* _compositor;
Component* _components;
Int _componentCount;
Int _lineWidth;
Int* _lineBreaks;
Int _lineCount;
};
当需要一个新的布局时,Composition让它的Compositor决定在何处换行。Composition传递给Compositor三个数组,它们定义给Component的数目、线的宽度以及一个数组,让Compositor来填充每次换行的位置。Compositor返回计算得到的换行数目。
Compositor接口使得Composition可传递给Compositor所有它需要的信息。此处是一个将数据传给Stragtegy的例子:
Class Compositor {
Public:
Virtual int Compose(
Coord natural[], Corrd stretch[], Coord shrink[],
Int compnentCount, int linewidth, int breaks[]
) = 0;
Protected:
Compositor();
};
注意上面定义的Compositor是一个抽象类,而其具体子类定义特定的换行策略。
Composition在它的Repair操作中调用它的Compositor。Repaire首先用每一个Component的正常大小、可伸展性和可收缩性初始化数组(略去细节)。然后它调用Compositor得到换行位置并最终据以对Component进行布局(省略):
Void Composition::Repair(){
Coord* natural;
Coord* stretchability;
Coord* shrinkability;
Int* breaks;
// prepare the arrays with the desired component sizes
// …
// determine where the breaks are:
Int breakCount;
breakCount = _compositor->Compose(
natural, stretchability, shrinkability,
componentCounts, _lineWidth, breaks
);
// lay out components according to breaks
// …
};
现在我们来看个Compositor子类。SimpleCompositor一次检查一行component,并决定在哪儿换行:
Class SimpleCompositor: public Compositor {
Public:
SimpleCompositor();
Virtural int Compose(
Coord natural[], Coord stretch[], Coord shrink[],
Int componentCount, int linewidth, int breaks[]
);
// …
};
TexCompositor使用一个更为全局的策略。它每次检查一个段落,并同时考虑到各Component的大小和伸展性。它也通过压缩Component之间的空白以尽量给该段落一个均匀的色彩。
Class TexCompositor: public Compositor {
Public:
TexCompositor();
Virtural int Compose(
Coord natural[], Coord stretch[], Coord shrink[],
Int componentCount, int linewidth, int breaks[]
);
// …
};
ArrayCompositor用规则的间距将构件分割成行。
Class ArrayCompositor: public Compositor {
Public:
ArrayCompositor(int interval);
Virtural int Compose(
Coord natural[], Coord stretch[], Coord shrink[],
Int componentCount, int linewidth, int breaks[]
);
// …
};
这些类并未都使用所有传递给Compose的信息。SimpleCompositor忽略Component的伸展性,仅考虑它们的正常大小;TeXCompositor使用所有传递给它的信息;而ArrayCompositor忽略所有的信息。
实例化Composition时需要把使用的Compositor传递给它:
Composition* quick = new Composition(new SimpleCompositor);
Composition* slick = new Composition(new TexCompositor);
Composition* iconic = new Composition(new ArrayCompositor(100));
Compositor的接口必须仔细设计,以支持子类能实现的所有的排版算法。不希望在生成一个新的子类不得不修改这个接口,因为这需要修改其他已有得子类。一般来说,Strategy和Context的接口决定了该模式能在多大程度上达到既定目的。
11.已知应用
ET++和InterViews都使用Strategy封装不同的换行算法。
在用于编译器代码优化的RTL系统中,Strategy定义了不同的寄存器分配方案
(RegisterAllocator)和指令集调度策略(RISCscheduler,CISCscheduler)。这就为在不同的目标机器结构上实现优化程序提供了所需的灵活性。
12. 相关模式
享元模式Flyweight:Strategy对象经常是很好的轻量级对象