加载中…
个人资料
LoveCelia
LoveCelia
  • 博客等级:
  • 博客积分:0
  • 博客访问:37,509
  • 关注人气:4
  • 获赠金笔:0支
  • 赠出金笔:0支
  • 荣誉徽章:
相关博文
推荐博文
正文 字体大小:

策略模式

(2007-09-11 22:26:32)
标签:

IT/科技

分类: Delphi
 

策略模式(Strategy)

目的(Intent)

定义一个算法(algorithms)家族(family);封装每一个成员;并让它们可以相互替换(interchangeable)。策略模式让算法变化独立于使用它的使用端。

动机(Motivation)

现有的许多处理文章断行的算法。直接将这些算法放入类中并不是好的方法;原因有:

●           需要断行的使用端如果将断行的程序代码包含进去会愈来愈复杂,这样让使用端愈来愈大并难以维护,尤其是如果支持多个断行算法。

●           不同的算法在不同时间有不同的适用性,如果我们不使用到时我们不想提供多重断行算法。

●           当断行是使用端一个完整的部分时难以增加新的算法也难以改变现有的算法。

我们可以定义类封装不同的断行算法以避免这些问题,封装成这种方式的算法称为策略对象。

假设一个TComposition类负责维护及更新显示于文字检视器中的文字断行,断行的策略不是由TComposition类实现。而是由区隔开的TCompositor抽象类的子类实现。TComposition子类实现不同的策略:

●           TSimpleCompositor:实现简单的策略,其决定断行一次一行。

●           TTeXCompositor:实现寻找断行的TEX算法,这个策略尝试以整体最佳化的断行;亦即一次整段。

●           TArrayCompositor:实现的策略是让每一行有固定数的项作为断行,例如将一群图示排列成行。

TComposition维护一个参考到TCompositor对象。每次TComposition重新排列其文字时;便将这个责任托付给TCompositor对象。TComposition的使用端指定哪一个TCompositor要被使用并安装这个想要的TCompositor到TComposition。

适用性(Applicability)

在下列情况下可使用策略模式:

●           当许多相关的类之间的差异只是其行为,策略模式提供一个组态(configure)一个类与许多行为中之一。

●           你需要一个算法的不同变化,例如;你可能定义算法反映不同的空间/时间交换使用。策略模式可以使用于当这些不同的实现是算法类层级架构时。

●           一个算法使用数据是使用端无法得知者。使用策略模式可以避免揭露复杂的算法特定的(algorithm-specific)数据结构。

●           一个类定义许多行为而且是多条件陈述操作。与其使用多条件,不如把这些条件分枝移到它们所拥有的策略类。

结构(Structure)

参与者(Participants)

●           TStrategy(TCompositor)

―            为所有支持的算法宣告一个共通的接口。TContext使用这个接口呼叫由TConcreteStrategy定义的算法。

●           TConcreteStrategy(TSimpleCompositor、TTeXCompositor、TArray- Compositor)

―            使用TStrategy接口实现算法。

●           TContext(TComposition)

―            以TConcreteStrategy对象组态其执行环境。

―            维护一个到TStrategy的参考。

―            可以定义一个接口供TStrategy存取其数据。

合作(Collaborations)

●           TStrategy及TContext互动以实现所选择的算法。当算法被呼叫时一个TContext对象可以经由算法传递所有需要的数据给TStrategy对象。此外TContext对象可以传递本身作为参数给TStrategy操作。这样在需要时让TStrategy回呼(call back)TContext。

●           TContext从其使用端传递请求给TStrategy。使用端常常构建并传递一个TConcreteStrategy对象给TContext,接着使用端与TConcreteStrategy独占的(exclusively)互动。一般有一家族的TConcreteStrategy供使用端选择使用。

结论(consequences)

策略模式一般有下列的利益及缺点:

1.          相关的算法家族:TStrategy类的层级架构为TContext定义一个算法或行为家族以便再使用。继承可以协助分解(factor out)算法通用的功能。

2.  继承以外的替代方案:继承提供其它方式支持算法或行为的多样性。你可以直接继承TContext类给他不同的行为,但这是硬接(hard-wires)行为到TContext中。它结合算法实现与TContext,使得TContext难以了解、维护及扩充。而且您无法动态的改变算法。你终结(wind up)了许多相关类而这些类的差异只是它们使用的算法或行为。封装这些算法在分开的TStrategy类中可以让你独立的改变算法于TContext之外,让它方便切换、了解及扩充。

3.  策略模式减少条件陈述:策略模式提供条件陈述以选择所要的行为的一种替代方案。当不同的行为混入一个类,变难以避免使用条件陈述以选择正确的行为。将行为封装在分开的TStrategy类中可以减少条件陈述。

例如:没有策略模式则断行的程序代码可能看起来像下面:

Procedure TCompsition.Repair();

Begin

  Case f_breakingstrategy of

    SimpleStrategy : ComposeWithSimpleCompositor();

    TeXStrategy : ComposeWithTeXCompositor();

//…..

  end;

//如果需要合并现有的文章成最后的结果

End;

策略模式因委托断行的工作给TStrategy对象而减少CASE的陈述:

Procedure TComposition.Repair();

Begin

  F_Compositor.Compose();

//如果需要合并现有的文章成最后的结果

End;

故如果程序代码包含许多条件陈述往往代表需要应用策略模式。

4.  实现的选择:策略模式可以提供相同的行为而有不同的实现,使用端可以在不同的时间点及空间替换的选择各种策略对象。

5.  使用端必须了解不同的策略:这个模式有一个潜在的缺点;使用端在选择其适当TStrategy对象时的必须了解其差异。使用端可能被暴露其实现议题,因此你只有在行为的变化是对使用端使用有意义时才使用策略模式。

6.  TStrategyTContext过度的沟通:TStrategy的接口由所有Tconcrete- Strategy类所共享不管它们实现的算法是简单的或复杂的。因此可能有些TConcreteStrategy并不经由所有的接口传递信息,简单的Tconcrete- Strategy甚至都不使用。这代表着当TContext构建并初始化参数都没有被使用。如果这是个值得讨论的议题;那么你必须紧密结合TStrategy及TContext。

7.  对象数量的增加:策略模式徒增应用程序的对象数量。有时你可以减低这种程度;方法是实现策略模式为无状态(stateless)对象而让TContext可以共享。任何残余(residual)状态都由TContext维护,TContext在每一个请求中传递它给TStrategy对象,共享TStrategy在请求过程中无须维护状态,在轻量模式(Flyweight)中描述这种方式更明确。2001/5/9

实现(implementation)

策略模式有下列实现的议题:

1.          定义TStrategyTContext接口:TStrategy及TContext接口必须让TconcreteStrategy从TContext有效存取所需要的数据。反之亦然。

一种方式是让TContext传递数据当作参数给TStrategy操作—也就是将数据带给TStrategy。这样使得TStrategy及TContext降低耦合性。从另一方面,TContext传的的参数是TStrategy不需要的。

其它的技术让TContext传递本身做为参数,而TStrategy从TContext得到明确的数据。其替代方案;TStrategy可以储存一个对TContext的参考,已降低传递任何数据的需求。这两种方式;TStrategy可以明确的请求它所需要的。但TContext必须对其数据定义更详细(elaborate)的接口,使得TStrategy及TContext耦合性更高。

特定算法及其数据取得的需求将决定最佳的技巧(technique)。

2.          TStrategy当作样版参数(template parameters):在C++中样版可以使用TStrategy以组态一个类。这个技术只有在(1) TStrategy在编译期可以被选择,(2)在执行期不能改变。此时被组态的类(如TContext)被定义成样版类;然后把TStrategy类当作参数。《译注:Delphi无样版功能》

//C++程序代码

template <class Astrategy>

class Context {

  void Operation(){theStrategy.DoAlgorithm();}

  //….

Private:

  Astrategy TheStrategy;

};

这个类在初始化时是以一个AStrategy类组态:

//C++程序代码

class MyStrategy{

public:

  void DoAlgorithm();

};

  Context<MyStrategy> aContext;

使用样版无须定义一个抽象类以定义Strategy的接口。使用Strategy为一个样版参数同时让你静态的结合Strategy其Context;如此可以增加效率。

3.          TStrategy对象可选择的(optional):TContext类可以简化;如果它是有意的不使用一个TStrategy对象。TContext在存取TStrategy前先检查是否存在有TStrategy对象,如果有那么TContext正常的使用它,如果没有那么TContext执行预设的行为。这种方式的优点是使用端无需与TStrategy对象打交道除非它们不喜欢预设的行为。

范例程序(Sample Code)

这个范例程序代码说明前面动机的例子;那是以InterViews中TComposition《译注:如写作的作品》及TCompositor《译注:排版机》的实现。

TComposition类维护一群成员(TComponent)实例对象;以显示一篇文章之文字及图形成员。一个TComposition使用TCompositor子类对象编排TComponent对象成一行一行的文章;其中TCompositor封装断行的策略。每一个TComponent有一个结合的自然外观尺寸(natural size)、延伸(stretchability)及退缩(shrinkability)。延伸定义TComponent在其自然外观尺寸范围内可以扩展的尺寸,退缩则是退缩的程度。TComposition传递这些值给TCompositor,TCompositor则使用这些值决定最佳的断行位置。

type

 

  TComposition = class

  private

    f_compositor: TCompositor;

    f_component: TComponent; //TComponent列表

    f_componentCount: integer; //TComponent数量

    f_lineWidth: integer; //TComposition的行宽

    f_lineBreaks: ^integer; //TComponent中断行的位置

 

    f_lineCount: integer; //行数

  public

    constructor Create(aCompositor: TCompositor);

    procedure Repair();

  end;

当需要一个新的配置(layout)时,TComposition向其TCompositor请求决定在哪里放置断行标志。TComposition传给TCompositor三个数组定义TComponent的外观尺寸、延伸及退缩,及TComponent数量、每行宽度及一个TComponent每一个断行的位置之数组,TCompositor回传计算的断行数量。

TCompositor的接口让TComposition传给TCompositor必要知道的所有的信息。这是一个「将资料带给Strategy」的例子。

type

  TCoord = array[0..99, 0..99] of integer;

 

  TCompositor = class

  protected

    constructor Create;

  public

    function Compose(natural, stretch, shink: TCoord;componentCount, lineWidth: integer; breaks: array of integer): integer; virtual; abstract;

  end;

请注意TCompositor是一个抽象类,其具体子类定义特定之断行策略。

TComposition在其Repair()操作中呼叫TCompositor,Repair()首先以所有TComponent的外观尺寸、延伸及退缩初始化数组(详细内容我们省略),然后呼叫TCompositor以得到断行及最后依据断行所得的TComponent配置。

procedure TComposition.Repair();

var

  natural, stretchability, shrinkability: TCoord;

  componentCount: integer;

  breaks: integer;

  breakCount: integer;

begin

  //依据所有的TComponent准备数组数据

  //.....

  //决定断行

  breakCount := f_compositor.Compose(natural, stretchability, shrinkability,

    componentCount, f_lineWidth, breaks);

  //依据断行所得的TComponent配置

  //......

end;

现在让我们看TCompositor的子类;TSimpleCompositor检验TComponent对象一次一行以决定断行应在哪里:

TSimpleComponentor = class(TCompositor)

  public

    constructor Create;

    function Compose(natural, stretch, shink: TCoord;

      componentCount, lineWidth: integer; breaks: integer): integer; virtual;

      //...

  end;

TteXComponentor使用一个更广泛的策略,它一次检验一段文章,它考虑TComponent的外观尺寸、延伸。它也尝试给段落一个平均的「颜色」以减少TComponent间的空白。

  TTeXComponentor = class(TCompositor)

  public

    constructor Create;

    function Compose(natural, stretch, shink: TCoord;

      componentCount, lineWidth: integer; breaks: integer): integer; virtual;

      //...

  end;

TarrayComponentor将TComponent以一定的间隔断行:

  TArrayComponentor = class(TCompositor)

  public

    constructor Create;

    function Compose(natural, stretch, shink: TCoord;

      componentCount, lineWidth: integer; breaks: integer): integer; virtual;

      //...

  end;

这些类都未使用所有传进Compose()中的信息。TSimpleComponentor忽略TComponent的延伸;只考虑其外观尺寸。TTeXComponentor使用所有传进的信息。TArrayComponentor忽略所有传进的信息。

TComposition实例对象初始,你必须传进你所要使用的TComponentor对象:

Var

S : TSimpleComponentor;

Quick : TComposition;

Begin

    S := TSimpleComponentor.Create;

Quick := TComposition.Create(s);

//….

End;

------------------------------------------------------------------------------------------------------

Var

T : TeXComponentor;

Slick : TComposition;

Begin

    T := TTeXComponentor.Create;

Slick := TComposition.Create(T);

//….

End;

------------------------------------------------------------------------------------------------------

Var

A : TArrayComponentor;

Iconic : TComposition;

Begin

    A := TArrayComponentor.Create;

Iconic := TComposition.Create(A);

//….

End;

TComponento的接口是小心的设计以支持所有其子类可能实现的配置算法,在新的子类中你不希望去修改这个接口,因为如此必须修改现有的子类。一般而言;TStrategy及TContext的接口决定了这个模式可成就的程度。

曾经使用的例子(Known uses)

在ET++及InterViews使用策略模式封装不同的断行算法如我们前面所述。

在RTL系统编译程序代码最佳化,策略模式定义不同的register allocation schema(RegisterAllocator)及instruction set scheduling policies(RISCscheduler、CISCscheduler)。这些提供弹性的指向不同的机制架构的最佳化。

ET++SwapsManager计算引擎框架依据不同的财务法则计算价格。其关键抽象是Instrument及YieldCurve。不同的法则实现成Instrument的子类,Yield- Curve计算折扣因素;以决定未来现金流之现有价格。这两个类都委托部分行为给策略对象。这个框架提供一个具体策略对象家族以产生现金流、价值兑换(valuing swaps)及计算折扣因素。你可以以具体策略对象组态Instrument及YieldCurve建立新的计算引擎。这种方式支持混和及配合现有的策略对象实现也可以定义新的。

Booch组件使用策略对象作为样版参数。Booch集合类支持三种内存配置策略:管理(managed)(配置于共享之外(allocation out of pool))、控制(controled)(配置\取消配置以锁定保护)及不管理(unmanaged)(一般的内存配置)。这些策略对象被当作样版参数当配置类构建对象时传递给它。例如一个UnboundedCollection使用不管理策略构建如

UnboundedCollection<MyItemType*, Unmanager>

RApp是一个整合回路配置(intergrated circuit layout)系统。RApp必须配置及路由回路中连接子系统的线路。RApp路由的算法被定义成一个抽象路由类的子类,路由类是一个策略类。

Borland的ObjectWindows使用策略模式于对话框以确保使用者输入正确的数据。例如;数字必须在一定的范围内,数字输入字段只准输入数字。评估一个字符串是否正确可以搜寻表格得知。

ObjectWindows使用Validator对象封装评估策略。Validator对象是策略对象的范例。数据输入字段委托评估策略给可选择的Validator对象。如果需要评估使用端把Validator对象依附于字段。当对话框关闭,输入字段要求它的Validator对象评估这个数据。这个类程序馆提供一般状况的评估,而如RangeValidate属于数字评估之用。新的使用端特定之评估策略可以方便的定义策略类的子类即可。

相关模式(Related Patterns)

轻量模式(Flyweight):策略对象一般使用轻量模式。2001/5/10

v

0

阅读 评论 收藏 转载 喜欢 打印举报/Report
前一篇:状态模式
后一篇:模板模式<br>
  • 评论加载中,请稍候...
发评论

    发评论

    以上网友发言只代表其个人观点,不代表新浪网的观点或立场。

    < 前一篇状态模式
    后一篇 >模板模式<br>
      

    新浪BLOG意见反馈留言板 电话:4000520066 提示音后按1键(按当地市话标准计费) 欢迎批评指正

    新浪简介 | About Sina | 广告服务 | 联系我们 | 招聘信息 | 网站律师 | SINA English | 会员注册 | 产品答疑

    新浪公司 版权所有