策略模式编程---转载(0分)

  • 主题发起人 主题发起人 degann
  • 开始时间 开始时间
D

degann

Unregistered / Unconfirmed
GUEST, unregistred user!
策略(Strategy)模式的用意是定义一组算法(algorithms),
并将每个算法封装到具有共同接口的独立的类中,从而使它们可以相互替换。
策略模式让算法变化独立于使用它的客户端。

unit HotelSaleStrategy;

interface

type
TSaleStrategy = class(TObject)
public
function SalePrice(Price: Currency
Value: Integer): Currency
virtual
abstract;
end;

TSeasonStrategy = class(TSaleStrategy)
public
function SalePrice(Price: Currency
Value: Integer): Currency
override;
end;

TVIPStrategy = class(TSaleStrategy)
public
function SalePrice(Price: Currency
Value: Integer): Currency
override;
end;

TTeamStrategy = class(TSaleStrategy)
public
function SalePrice(Price: Currency
Value: Integer): Currency
override;
end;

TPriceContext = class(TObject)
private
FStrategy: TSaleStrategy;
procedure SetStrategy(Value: TSaleStrategy);
public
function GetPrice(Price: Currency
Value: Integer): Currency;
property Strategy: TSaleStrategy read FStrategy write SetStrategy;
end;

implementation

{TSeasonStrategy}

function TSeasonStrategy.SalePrice(Price: Currency
Value: Integer): Currency;
begin
//季節銷售策略
{
2, 3, 11月8.5折優惠
4, 6月9折優惠
8, 9月9.5折優惠
}
case Value of
2, 3, 11: Result := Price * 0.85;
4, 6: Result := Price * 0.9;
8, 9: Result := Price * 0.95;
else
Result := Price;
end;
end;

{TVIPStrategy}

function TVIPStrategy.SalePrice(Price: Currency
Value: Integer): Currency;
begin
//VIP卡銷售策略
{
0: VIP銀卡 9折優惠
1: VIP金卡 8折優惠
2: VIP鑽石卡 7折優惠
}
case Value of
0: Result := Price * 9;
1: Result := Price * 8;
2: Result := Price * 7
end;
end;

{TTeamStrategy}

function TTeamStrategy.SalePrice(Price: Currency
Value: Integer): Currency;
begin
//團隊銷售策略
{
3-5人團隊9折優惠
6-10人團隊8折優惠
11-20人團隊7折優惠
20人以上團隊6折優惠
}
Result := Price;
if (Value < 6) and (Value > 2) then Result := Price * 0.9 else
if (Value < 11) and (Value >5) then Result := Price * 0.8 else
if (Value < 21) and (Value > 10) then Result := Price * 0.7 else
if Value > 20 then Result := Price * 0.6;
end;

{TPriceContext}

function TPriceContext.GetPrice(Price: Currency
Value: Integer): Currency;
begin
Result := Strategy.SalePrice(Price, Value);
end;

procedure TPriceContext.SetStrategy(Value: TSaleStrategy);
begin
FStrategy := Value;
end;

end.



unit ClientForm;

interface

uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, HotelSaleStrategy, StdCtrls, ComCtrls, ExtCtrls, DateUtils;

type
TClient = class(TForm)
RadioGroup1: TRadioGroup;
dtpDate: TDateTimePicker;
cmbVIP: TComboBox;
edtCount: TEdit;
cmbPrice: TComboBox;
btnCheck: TButton;
edtPrice: TEdit;
btnExit: TButton;
Label1: TLabel;
Label2: TLabel;
Label3: TLabel;
Label4: TLabel;
Label5: TLabel;
Bevel1: TBevel;
procedure RadioGroup1Click(Sender: TObject);
procedure btnCheckClick(Sender: TObject);
procedure btnExitClick(Sender: TObject);
private
FSeasonStrategy: TSaleStrategy;
FVIPStrategy: TSaleStrategy;
FTeamStrategy: TSaleStrategy;
FPriceSys: TPriceContext;
end;

var
Client: TClient;

implementation

{$R *.dfm}

procedure TClient.RadioGroup1Click(Sender: TObject);
begin
dtpDate.Enabled := False;
cmbVIP.Enabled := False;
edtCount.Enabled := False;

Case RadioGroup1.ItemIndex of
0: dtpDate.Enabled := True;
1: cmbVIp.Enabled := True;
2: edtCount.Enabled := True;
end;
end;

(*$WARNINGS OFF*)

procedure TClient.btnCheckClick(Sender: TObject);
var
I: Integer;
Price: Currency;
begin
try
FSeasonStrategy := TSeasonStrategy.Create;
FVIPStrategy := TVIPStrategy.Create;
FTeamStrategy := TTeamStrategy.Create;
FPriceSys := TPriceContext.Create;

case RadioGroup1.ItemIndex of
0: begin
FPriceSys.Strategy := FSeasonStrategy;
I := MonthOf(dtpDate.DateTime);
end;
1: begin
FPriceSys.Strategy := FVIPStrategy;
I := cmbVIP.ItemIndex;
end;
2: begin
FPriceSys.Strategy := FTeamStrategy;
I := StrToInt(edtCount.Text);
end;
end;

case cmbPrice.ItemIndex of
0: Price := 300;//甲類標准間300元
1: Price := 500;//乙類標准間500元
2: Price := 800;//貴賓間800元
3: Price := 1000;//商務套房1000元
4: Price := 2000;//豪華套房2000元
end;

edtPrice.Text := CurrToStr(FPriceSys.GetPrice(Price, I));
finally
FSeasonStrategy.Free;
FVIPStrategy.Free;
FTeamStrategy.Free;
FPriceSys.Free;
end;

end;

(*$WARNINGS ON*)

procedure TClient.btnExitClick(Sender: TObject);
begin
Close;
end;

end.
 
代码太多了
楼主具体介绍下概验
 
Delphi模式编程之策略模式

刘 艺

1.1 模式解说
策略(Strategy)模式的用意是定义一组算法(algorithms),并将每个
算法封装到具有共同接口的独立的类中,从而使它们可以相互替换。策略
模式让算法变化独立于使用它的客户端。
要了解策略模式的使用动机和意义,我们得先从一个有趣的例子说起。
在一个物料管理系统中,出库和入库模块是该系统的核心部分(下面我
们以出库为例进行分析)。
对于一个没有面向对象编程经验的程序员,他们往往会把出库的所有
逻辑都放在客户端(出库单界面),并在客户端利用条件分支语句来
判断该出库单类型是领料、借料还是报损,以便选择不同的出库结算
方法,如图所示。这样一来,客户端的代码就变得复杂和难以维护。
比如:需要新增调拨单类型的出库时,就要修改判断条件,重新编译和
发布客户端。当情况愈来愈复杂,条件分支会愈来愈多,添加的程序代码
也会愈来愈多,这样让客户端愈来愈大并难以维护,互相影响和出错的可能性增大。


图 基于面向过程思想设计的出库模块
如果用面向对象的思想来分析,可以把领料单、借料单、报损单看作是
出库单的派生类,如图 所示。这样出库单作为单据基类提供单据的
共同接口,而利用继承的办法在子类里实现不同的出库行为。这实际上
利用了面向对象里的一个重要概念:多态。
但是这样的设计还有美中不足的地方,这就是环境和行为紧密耦合在一起。
也就是说,单据和具体出库的算法紧密耦合在一起。强耦合使得两者不
能独立演化,限制了重用性和扩展性。
是利用策略模式重新设计的出库模块。出库单据对象通过一个出
库操作对象(即策略模式中的Context)来引用出库策略对象。各种具
体的出库策略则由出库策略类的派生类实现。出库单据可以由出库操作
和单据样式分别提供出库结算方法和单据显示界面。这样,策略模式就
把出库的行为从出库单据的环境中独立出来,出库算法的增减、修改都
不会影响到环境和客户端。

基于面向对象思想设计的出库模块

基于设计模式思想设计的出库模块
策略模式的优势在于算法和环境的分离,两者可以独立演化。为了更好
地说明算法和环境分离的好处,我们不妨看一下的设计。在这个
设计中,已经没有出库和入库模块的概念,因为我将所有出/入库单据
抽象出来,在运行期动态组合单据的界面和行为。通过出/入库操作类,
可以维护、查询、配置不同的行为类。抽象出的出/入库行为以策略类的
方式封装了其对应的算法,以便完成不同类型的出入库单据的操作。这
就显而易见地提高了系统的重用性和可扩展性,减低维护的难度。

策略模式的优势在于算法和环境的分离,两者可以独立演化
由此可见,策略模式适用于以下情形:
· 当许多相关的类之间的差异只在于其行为时。策略模式可
以动态地让一个对象在许多行为中选择一种行为。
· 当实现一个目的有多种可选算法时,比如:你出于不同的
利弊权衡考虑定义的那些算法(即相当于应用不同的策略)。这些具体
的算法可以封装成抽象算法类的派生类,并享用该抽象算法类的统一接口。
通过多态性,客户端只要持有一个抽象算法类的对象,就可以选用任
何一个具体的算法。
· 当一个算法使用的数据不可以让客户端得知时。使用策
略模式可以避免暴露复杂的与算法相关的数据结构。其实客户端也没
有必要知道这些与算法相关的知识和数据。
· 当一个类定义有很多行为,且用多个条件语句来判断选择
这些行为时。策略模式可以把这些行为转移到对应的具体策略类中,
从而避免了难以维护的多重条件选择,体现了面向对象的编程思想。
1.2 结构与用法
策略模式的结构如所示,它包括了以下参与者:
· 抽象策略(TStrategy)——为所有支持的算法声明一个
共同的接口。TContext使用这个接口调用由TConcreteStrategy定义和
封装的算法。
· 具体策略(TConcreteStrategy)——封装了具体算法或
行为。实现TStrategy接口。
· 上下文(TContext)——持有一个到TStrategy的引用。
调用TStrategy接口,动态配置具体算法或行为。

策略模式的结构
在策略模式中,通过TStrategy和TContext的交互实现所选择的算法。
当算法被调用时, TContext可以将该算法所需要的所有数据都传递给
该TStrategy。或者,TContext可以将自身作为一个参数传递给
TStrategy操作。
当TContext将客户端请求转发给它的TStrategy时,客户通常创建并
传递一个TConcreteStrategy对象给该TContext;这样, 客户端仅
与TContext交互。通常有一系列的TConcreteStrategy类可供客户端
从中选择。
Delphi模式编程之策略模式(续)

刘 艺
1.3 策略模式在酒店管理系统中的应用
在酒店管理系统中,通常客房的价格不是一成不变的。对于住宿的
淡季和旺季、老客户和新客户、散客和团队,都应该有不同的销售策略。
显然,销售策略决定了报价。但是基于销售策略的报价体系又不能绑定
于某一具体的客户端,因为只有把基于销售策略的报价体系独立出来,
才能保证其重用性和可维护性。比如:一种报价体系一方面满足了
优惠房价查询、客房结算等多个客户端的使用,另一方面又满足了
不断调整的新销售策略的需求,这才算真正做到了重用性和可维护性。
对于以上的设计要求,选用策略模式是最好不过了。策略模式能够
让算法变化独立于使用它的客户端。范例程序是一个基于策略模式的
优惠房价查询模块,它包括一个基于销售策略的报价体系和一个优惠房
价查询界面。当然,优惠房价查询界面只是该报价体系的客户端之一,
报价体系亦可被其他客户端使用。
优惠房价查询模块的设计所示。它包括了:
· 销售策略类TSaleStrategy,它是具体销售策略类的抽象基类。
· 3个具体销售策略类:TVIPStrategy (VIP卡销售策略)
、TTeamStrategy (团队销售策略)、TSeasonStrategy(季节销售策略)。
· 报价类TPriceContext,它是该策略模式中的上下文,
持有一个到TStrategy的引用。
· 客户端类TClient,它是一个窗体类,即房价查询的界面。


基于策略模式的优惠房价查询模块
示例程序是HotelSaleStrategy单元的源代码,该单元包含了基于
销售策略的报价体系的业务逻辑,用策略模式实现。TSaleStrategy
作为销售策略的抽象基类,其目的是提供一个通用的接口。虚抽象函数
SalePrice就是这样一个接口。由于3个具体销售策略分别是根据
季节、VIP卡、团队人数来制定销售策略的,所以基类接口SalePrice
的参数设计必须满足3个派生类的不同需求。TSaleStrategy的
SalePrice函数声明如下:

function SalePrice(price:Currency;value:integer):Currency

virtual
abstract;


它的第一个参数表示传入的固定房价,第二个参数表示传入的优惠条件,
该条件因不同的派生类而异。在季节销售策略TSeasonStrategy中,
该参数表示为入住月份;在VIP卡销售策略TVIPStrategy中,
该参数表示为VIP卡的种类;在团队销售策略TTeamStrategy中,
该参数表示为团队人数。我们发现,这些参数都可以用整数类型,
所以在基类中,巧妙地用一个value参数解决了派生类的不同参数需求。
这样一来,可以直接让TPriceContext将数据放在参数中传递给不
同的销售策略类操作,避免了参数冗余。

{TPriceContext }
function TPriceContext.GetPrice(price:Currency;value:integer):
Currency;
begin
result:=Strategy.SalePrice(price,value);
end;

TPriceContext在该策略模式中起着上下文作用,它负责引用销
售策略对象的不同实例,调用SalePrice接口,动态配置具体的折扣算法,
并返回实际销售价格。由于有了TPriceContext的中介,
客户端无需知道具体销售策略是如何实现的;同样,
当销售策略进行更新调整时,对客户端程序亦无影响。
示例程序 HotelSaleStrategy单元的源代码
unit HotelSaleStrategy;

interface

uses
SysUtils, Windows, Messages, Classes, Graphics, Controls,
Forms, Dialogs;

type
TSaleStrategy = class (TObject)
public
function SalePrice(price:Currency;value:integer):Currency

virtual
abstract;
end;

TSeasonStrategy = class (TSaleStrategy)
public
function SalePrice(price:Currency;value:integer):Currency
override;
end;

TVIPStrategy = class (TSaleStrategy)
public
function SalePrice(price:Currency;value:integer):Currency
override;
end;

TTeamStrategy = class (TSaleStrategy)
public
function SalePrice(price:Currency;value:integer):Currency
override;
end;

TPriceContext = class (TObject)
private
FStrategy: TSaleStrategy;
procedure SetStrategy(Value: TSaleStrategy);
public
function GetPrice(price:Currency;value:integer):Currency;
property Strategy: TSaleStrategy read FStrategy write SetStrategy;
end;

implementation

{TSeasonStrategy }
function TSeasonStrategy.SalePrice(price:Currency;value:integer):Currency;
begin
//季节销售策略
{
2、3、11月8.5折优惠,
4、6月9折优惠。
8、9月9.5折优惠。
}
case value of
2,3,11:result:=price*0.85;
4,6:result:=price*0.9;
8,9:result:=price*0.95;
else
result:=price;
end;
end;

{TVIPStrategy }
function TVIPStrategy.SalePrice(price:Currency;value:integer):Currency;
begin
//VIP卡销售策略
{
0:VIP银卡 9折优惠
1:VIP金卡 8折优惠
2:VIP钻石卡 7 折优惠
}

case value of
0:result:=price*0.9;
1:result:=price*0.8;
2:result:=price*0.7;
end;
end;

{TTeamStrategy }
function TTeamStrategy.SalePrice(price:Currency;value:integer):Currency;
begin
//团队销售策略
{
3-5人团队9折优惠;
6-10人团队8折优惠;
11-20人团队7折优惠;
20人以上团队6折优惠。
}
result:=price;
if (value<6) and (value>2) then result:=price*0.9;
if (value<11) and (value>5) then result:=price*0.8;
if (value<21) and (value>10) then result:=price*0.7;
if (value>20) then result:=price*0.6;
end;

{TPriceContext }
function TPriceContext.GetPrice(price:Currency;value:integer):Currency;
begin
result:=Strategy.SalePrice(price,value);
end;

procedure TPriceContext.SetStrategy(Value: TSaleStrategy);
begin
FStrategy:=Value;
end;

end.

优惠房价查询模块的客户端程序如錯誤! 找不到參照來源。所示。
该程序提供一个用户选择界面,使得查询者可以任选一种优惠方案。
一旦选定优惠条件和公开房价,点击“查询优惠房价”按钮,便得到
打折后的优惠价。实际运行效果如所示。
示例程序ClientForm单元的源代码
unit ClientForm;

interface

uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, ExtCtrls,HotelSaleStrategy, ComCtrls,DateUtils;

type
TClient = class(TForm)
RadioGroup1: TRadioGroup;
btnCheck: TButton;
btnExit: TButton;
dtpDate: TDateTimePicker;
cmbVIP: TComboBox;
Label1: TLabel;
Label2: TLabel;
cmbPrice: TComboBox;
edtPrice: TEdit;
Label3: TLabel;
edtCount: TEdit;
Label4: TLabel;
Label5: TLabel;
Bevel1: TBevel;
procedure FormCreate(Sender: TObject);
procedure btnCheckClick(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure btnExitClick(Sender: TObject);
procedure RadioGroup1Click(Sender: TObject);
private
FSeasonStrategy:TSaleStrategy;
FVIPStrategy:TSaleStrategy;
FTeamStrategy:TSaleStrategy;
FPriceSys:TPriceContext;
public
{ Public declarations }
end;

var
Client: TClient;

implementation


{$R *.dfm}

procedure TClient.FormCreate(Sender: TObject);
begin
FSeasonStrategy:=TSeasonStrategy.Create;
FVIPStrategy:=TVIPStrategy.Create;
FTeamStrategy:=TTeamStrategy.Create;
FPriceSys:=TPriceContext.Create;
end;

procedure TClient.btnCheckClick(Sender: TObject);
var
i:integer;
price:Currency;
begin
case RadioGroup1.ItemIndex of
0:begin
FPriceSys.Strategy:=FSeasonStrategy ;
i:=MonthOf(dtpDate.DateTime);
end;
1:begin
FPriceSys.Strategy:=FVIPStrategy ;
i:=cmbVIP.ItemIndex;
end;
2:begin
FPriceSys.Strategy:=FTeamStrategy ;
i:=StrToInt(edtCount.Text);
end;
end;
case cmbPrice.ItemIndex of
0:price:=300
//甲类标准间300元
1:price:=500
//乙类标准间500元
2:price:=800
//贵宾间800元
3:price:=1000
//商务套房1000元
4:price:=2000
// 豪华套房2000元
end;
edtPrice.Text:=CurrToStr(FPriceSys.GetPrice(price,i));
end;

procedure TClient.FormDestroy(Sender: TObject);
begin
FPriceSys.Free;
FSeasonStrategy.Free;
FVIPStrategy.Free;
FTeamStrategy.Free;
end;

procedure TClient.btnExitClick(Sender: TObject);
begin
close;
end;

procedure TClient.RadioGroup1Click(Sender: TObject);
begin
dtpDate.Enabled:=false;
edtCount.Enabled:=false;
cmbVIP.Enabled:=false;
case RadioGroup1.ItemIndex of
0:dtpDate.Enabled:=true;
1:cmbVIP.Enabled:=true;
2:edtCount.Enabled:=true;
end;
end;

end.

优惠房价查询模块的实际运行界面
1.4 实践小结
通过前面范例的演示和剖析,我们进一步讨论策略模式如下:
· 策略模式提供了管理算法集的办法。策略类的层次结构为
TContext定义了一系列的可供重用的算法或行为。TStrategy基类析取
出这些算法中的公共功能,派生类通过继承丰富了算法的差异和种类,
又避免了重复的代码。
· 如果不将算法和使用算法的上下文分开,直接生成一个包
含算法的TContext类的派生类,给它以不同的行为,这将会把行为写死
到TContext中,而将算法的实现与TContext的实现混合起来,从而使
TContext难以理解、难以维护和难以扩展。最后得到一大堆相关的类,
它们之间的唯一差别是它们所使用的算法。显然,类的继承关系是强
关联,继承关系无法动态地改变算法;而对象的合成关系是弱关联,
通过组合策略类对象,使得算法可以独立于使用算法的环境
(TContext)而独立演化。
· 使用策略模式可以对大量使用条件分支语句的程序
代码进行重构。当不同的行为堆砌在一个类中时,很难避免使用
条件语句来选择合适的行为。将行为封装在一个个独立的策略类
中消除了这些条件语句。
· 过多的算法可能会导致策略对象的数目很大。为了
减少系统开销,通常可以把依赖于算法环境的状态保存在客户端,
而将TStrategy实现为可供各客户端共享的无状态的对象。任何
外部的状态都由TContext维护。TContext在每一次对TStrategy对象
的请求中都将这个状态传递过去。比如范例程序中,我将TSeasonStrategy
的外部状态入住月份、TVIPStrategy的外部状态VIP卡的种类、TTeamStrategy
的外部状态团队人数都保存在客户端,并通过TPriceContext将这些状态传
递给销售策略类。这样做的好处是销售策略类变成无状态的了,
它们同时可以被客房结算模块等其他模块共享。
· 无论各个具体策略实现的算法是简单还是复杂,
它们都共享TStrategy定义的接口。因此很可能某些具体策略不会都
用到所有通过这个接口传递给它们的信息。如果我在范例程序中把
TSaleStrategy的接口设计成这样:
SalePrice(price:Currency;Month:integer;VIP:integer

Count:integer):Currency;
其中的一些参数永远不会被某些具体销售策略类用到。
这就意味着有时TContext会创建和初始化一些永远不会用到的参数。
如果存在这样问题,又无法使用范例程序中的技巧,
那么只能在TStrategy和TContext之间采取紧耦合的方法。
更多相关文章和示例程序源代码可以到作者网站下载:http://www.liu-yi.net
 
找到了﹗﹗﹗
http://dev.csdn.net/article/27/27715.shtm
 
原代码在刘艺的网站有的下载的
http://www.liu-yi.net/
 
后退
顶部