示例:影片出租店程序(重构——引入状态模式)
由于考虑到“影片分类结构”、“费用计算规则”或“常客积点计算规则”在未来可能会发生改变,所以通过引入“状态模式”将“费用计算”和“常客积点计算”中“因条件而异的代码”替换掉。
步骤:
1、移动“金额计算”方法 —— “搬移方法(Move Method)”
由于Rental类的GetCharge方法中使用到了Movie类的属性,所以这暗示应将GetCharge方法移动到Movie类中。
function TMovie.GetCharge(DaysRented:integer): double;
{计算一笔租片费用}
begin
Result := 0;
//---
case self.PriceCode of //--取得影片出租价格
REGULAR: //--普通片
begin
Result := Result + 2;
if DaysRented > 2 then
Result := Result + (DaysRented - 2) * 1.5;
end;
NEW_RELEASE: //--新片
begin
Result := Result + DaysRented * 3;
end;
CHILDRENS: //--儿童片
begin
Result := Result + 1.5;
if DaysRented > 3 then
Result := Result + (DaysRented - 3) * 1.5;
end;
end;
end;
function TRental.GetCharge: double;
begin
Result := self.Movie.GetCharge(self.DaysRented);
end;
2、移动“常客积点计算”方法 —— “搬移方法(Move Method)”
function TMovie.GetFrequentRenterPoints(DaysRented:integer): integer;
{获取常客积点}
begin
if (self.PriceCode = NEW_RELEASE) and (DaysRented > 1) then
Result := 2
else
Result := 1;
end;
function TRental.GetFrequentRenterPoints: integer;
begin
Result := self.Movie.GetFrequentRenterPoints(self.DaysRented);
end;
3、重构“费用计算”方法 —— 引入状态模式
运用“状态模式”取代与价格相关的条件逻辑。
3.1、替换“价格类型码”——“替换类型码(Replace Type Code with State/Strategy)”
运用“替换类型码” 将“与型别相依的行为”搬移至“状态类”内。
1、封装“价格字段”——“封装字段(Self Encapsulate Field)”
第一步是针对“与型别相依的行为”使用“封装字段” ,确保任何时候都通过getting和setting两个函数来运用这些行为。
constructor TMovie.Create(const ATitle: string; APriceCode: integer);
begin
FTitle := ATitle;
Self.PriceCode := APriceCode; //译注:这就是一个set method
end;
2、创建“状态类”
第二步是实现price类,并在其中提供“与型别相依的行为”。
type
TPrice = class
protected
function GetPriceCode: integer; virtual; abstract; //--取得价格代号
public
property PriceCode: integer read GetPriceCode;
end;
TChildrensPrice = class(TPrice)
protected
function GetPriceCode: integer; override;
end;
TNewReleasePrice = class(TPrice)
protected
function GetPriceCode: integer; override;
end;
TRegularPrice = class(TPrice)
protected
function GetPriceCode: integer; override;
end;
function TChildrensPrice.GetPriceCode: integer;
begin
Result := CHILDRENS;
end;
function TNewReleasePrice.GetPriceCode: integer;
begin
Result := NEW_RELEASE;
end;
function TRegularPrice.GetPriceCode: integer;
begin
Result := REGULAR;
end;
3、修改“价格代号”访问函数
第三步,修改Movie类内的“价格代号”访问函数, 让它们使用Price类。
type
TMovie = class
Private
…………
FPrice:TPrice; //--价格代号
function GetPriceCode: integer;
procedure SetPriceCode(const Value: integer);
…………
end;
function TMovie.GetPriceCode: integer;
{取得价格代号}
begin
Result := FPrice.PriceCode;
end;
procedure TMovie.SetPriceCode(const Value: integer);
{设置价格代号}
begin
if FPrice <> nil then
FPrice.Free;
//---
case Value of
REGULAR: //--普通片
begin
FPrice := TRegularPrice.Create;
end;
NEW_RELEASE: //--新片
begin
FPrice := TNewReleasePrice.Create;
end;
CHILDRENS: //--儿童片
begin
FPrice := TChildrensPrice.Create;
end;
else
raise IllegalArgumentException.Create('Incorrect Price Code');
end;
end;
3.2、搬移“费用计算”方法 ——“搬移方法(Move Method)”
对Movie.GetCharge运用“搬移方法”将switch语句移到Price类里头。
function TPrice.GetCharge(DaysRented: integer): double;
{计算一笔租片费用}
begin
Result := 0;
//---
case self.PriceCode of //--取得影片出租价格
REGULAR: //--普通片
begin
Result := Result + 2;
if DaysRented > 2 then
Result := Result + (DaysRented - 2) * 1.5;
end;
NEW_RELEASE: //--新片
begin
Result := Result + DaysRented * 3;
end;
CHILDRENS: //--儿童片
begin
Result := Result + 1.5;
if DaysRented > 3 then
Result := Result + (DaysRented - 3) * 1.5;
end;
end;
end;
function TMovie.GetCharge(DaysRented: integer): double;
{计算一笔租片费用}
begin
Result := FPrice.GetCharge(DaysRented);
end;
3.3、替换case语句——“用多态替换条件语句(Replace Condional with Polymorphism)”
最后运用“用多态替换条件语句”去掉Price.GetCharge方法中的case语句。
1、提取REGULAR分支
作法是每次取出一个case分支,在相应的 class 内建立一个“覆写方法(overriding method)”。
先从RegularPrice类开始,RegularPrice.GetCharge方法覆写了父类中的case分支语句。
type
TPrice = class
protected
function GetPriceCode: integer; virtual; abstract; //--取得价格代号
public
function GetCharge(DaysRented: integer): double; virtual;
//---
property PriceCode: integer read GetPriceCode;
end;
TRegularPrice = class(TPrice)
protected
function GetPriceCode: integer; override;
public
function GetCharge(DaysRented: integer): double; override;
end;
function TRegularPrice.GetCharge(DaysRented: integer): double;
{计算一笔租片费用}
begin
Result := 2;
if DaysRented > 2 then
Result := Result + (DaysRented - 2) * 1.5;
end;
2、提取NEW_RELEASE分支
function TNewReleasePrice.GetCharge(DaysRented: integer): double;
{计算一笔租片费用}
begin
Result := DaysRented * 3;
end;
3、提取CHILDRENS分支
function TChildrensPrice.GetCharge(DaysRented: integer): double;
{计算一笔租片费用}
begin
Result := 1.5;
if DaysRented > 3 then
Result := Result + (DaysRented - 3) * 1.5;
end;
4、处理Price.GetCharge
处理完所有case分支之后,把Price.GetCharge声明为abstract。
type
TPrice = class
protected
function GetPriceCode: integer; virtual; abstract; //--取得价格代号
public
function GetCharge(DaysRented: integer): double; virtual; abstract;
//---
property PriceCode: integer read GetPriceCode;
end;
4、重构“常客积点计算”方法 —— 引入状态模式
运用“状态模式”取代Movie.GetFrequentRenterPoints方法中“与型别相依的行为”,也就是“判断是否为新片self.PriceCode = NEW_RELEASE”那个动作。
4.1、搬移“常客积点计算”方法 ——“搬移方法(Move Method)”
function TPrice.GetFrequentRenterPoints(DaysRented: integer): integer;
{获取常客积点}
begin
if (self.PriceCode = NEW_RELEASE) and (DaysRented > 1) then
Result := 2
else
Result := 1;
end;
function TMovie.GetFrequentRenterPoints(DaysRented: integer): integer;
{获取常客积点}
begin
Result := FPrice.GetFrequentRenterPoints(DaysRented);
end;
4.2、替换条件语句——“用多态替换条件语句(Replace Condional with Polymorphism)”
不需声明TPrice.GetFrequentRenterPoints函数为abstract,使其成为一种缺省行为。只为“新片类型”产生一个覆写函数。
Type
TPrice = class
public
function GetFrequentRenterPoints(DaysRented: integer): integer; virtual;
end;
TNewReleasePrice = class(TPrice)
public
function GetFrequentRenterPoints(DaysRented: integer): integer; override;
end;
function TPrice.GetFrequentRenterPoints(DaysRented: integer): integer;
{获取常客积点}
begin
Result := 1;
end;
function TNewReleasePrice.GetFrequentRenterPoints(DaysRented: integer): integer;
{获取常客积点}
begin
if (DaysRented > 1) then
Result := 2
else
Result := 1;
end;
代码:
interface
uses
Windows, Messages, SysUtils, Contnrs;
const
REGULAR = 0;
NEW_RELEASE = 1;
CHILDRENS = 2;
type
TPrice = class;
TEnumeration = class
private
FList: TObjectList;
FIndex: integer;
public
constructor Create(const AList: TObjectList);
//---
function HasMoreElements: boolean;
function NextElement: TObject;
end;
//--影片
TMovie = class
private
FTitle: string; //--名称
FPrice: TPrice; //--价格代号
function GetTitle: string;
function GetPriceCode: integer;
procedure SetPriceCode(const Value: integer);
public
constructor Create(const ATitle: string; APriceCode: integer);
destructor Destroy; override;
//---
function GetCharge(DaysRented: integer): double;
function GetFrequentRenterPoints(DaysRented: integer): integer;
//---
property Title: string read GetTitle;
property PriceCode: integer read GetPriceCode write SetPriceCode;
end;
//--租赁
TRental = class
private
FMovie: TMovie;
FDaysRented: integer; //--租期
function GetDaysRented: integer;
function GetMovie: TMovie;
public
constructor Create(const AMovie: TMovie; ADaysRented: integer);
//---
function GetCharge: double; //---计算一笔租片费用
function GetFrequentRenterPoints: integer;
//---
property Movie: TMovie read GetMovie;
property DaysRented: integer read GetDaysRented;
end;
//--顾客
TCustomer = class
private
FName: string;
FRentals: TObjectList;
function GetName: string;
function GetTotalCharge: double;
function GetTotalFrequentRenterPoints: integer;
public
constructor Create(const AName: string);
destructor Destroy; override;
//---
procedure AddRental(arg: TRental);
function Statement: string; //--统计报表
function HtmlStatement: string;
//---
property Name: string read GetName;
end;
TPrice = class
protected
function GetPriceCode: integer; virtual; abstract; //--取得价格代号
public
function GetCharge(DaysRented: integer): double; virtual; abstract;
function GetFrequentRenterPoints(DaysRented: integer): integer; virtual;
//---
property PriceCode: integer read GetPriceCode;
end;
TChildrensPrice = class(TPrice)
protected
function GetPriceCode: integer; override;
public
function GetCharge(DaysRented: integer): double; override;
end;
TNewReleasePrice = class(TPrice)
protected
function GetPriceCode: integer; override;
public
function GetCharge(DaysRented: integer): double; override;
function GetFrequentRenterPoints(DaysRented: integer): integer; override;
end;
TRegularPrice = class(TPrice)
protected
function GetPriceCode: integer; override;
public
function GetCharge(DaysRented: integer): double; override;
end;
IllegalArgumentException = class(Exception)
end;
implementation
const
CRLF = #13#10;
constructor TMovie.Create(const ATitle: string; APriceCode: integer);
begin
FTitle := ATitle;
Self.PriceCode := APriceCode; //译注:这就是一个set method
end;
destructor TMovie.Destroy;
begin
if FPrice <> nil then
FPrice.Free;
//---
inherited;
end;
function TMovie.GetCharge(DaysRented: integer): double;
{计算一笔租片费用}
begin
Result := FPrice.GetCharge(DaysRented);
end;
function TMovie.GetFrequentRenterPoints(DaysRented: integer): integer;
{获取常客积点}
begin
Result := FPrice.GetFrequentRenterPoints(DaysRented);
end;
function TMovie.GetPriceCode: integer;
{取得价格代号}
begin
Result := FPrice.PriceCode;
end;
function TMovie.GetTitle: string;
begin
Result := FTitle;
end;
procedure TMovie.SetPriceCode(const Value: integer);
{设置价格代号}
begin
if FPrice <> nil then
FPrice.Free;
//---
case Value of
REGULAR: //--普通片
begin
FPrice := TRegularPrice.Create;
end;
NEW_RELEASE: //--新片
begin
FPrice := TNewReleasePrice.Create;
end;
CHILDRENS: //--儿童片
begin
FPrice := TChildrensPrice.Create;
end;
else
raise IllegalArgumentException.Create('Incorrect Price Code');
end;
end;
constructor TRental.Create(const AMovie: TMovie; ADaysRented: integer);
begin
FMovie := AMovie;
FDaysRented := ADaysRented;
end;
function TRental.GetCharge: double;
begin
Result := self.Movie.GetCharge(self.DaysRented);
end;
function TRental.GetDaysRented: integer;
begin
Result := FDaysRented;
end;
function TRental.GetFrequentRenterPoints: integer;
begin
Result := self.Movie.GetFrequentRenterPoints(self.DaysRented);
end;
function TRental.GetMovie: TMovie;
begin
Result := FMovie;
end;
constructor TCustomer.Create(const AName: string);
begin
FName := AName;
FRentals := TObjectList.Create;
end;
destructor TCustomer.Destroy;
begin
FRentals.Free;
//---
inherited;
end;
procedure TCustomer.AddRental(arg: TRental);
begin
FRentals.Add(arg);
end;
function TCustomer.GetName: string;
begin
Result := FName;
end;
function TCustomer.GetTotalCharge: double;
{总消费金额}
var
Rentals: TEnumeration;
each: TRental;
begin
Result := 0;
//---
Rentals := TEnumeration.Create(FRentals);
while Rentals.HasMoreElements do
begin
each := TRental(Rentals.NextElement);
Result := Result + each.GetCharge;
end;
Rentals.Free;
end;
function TCustomer.GetTotalFrequentRenterPoints: integer;
{总常客积点}
var
Rentals: TEnumeration;
each: TRental;
begin
Result := 0;
//---
Rentals := TEnumeration.Create(FRentals);
while Rentals.HasMoreElements do
begin
each := TRental(Rentals.NextElement);
Result := Result + each.GetFrequentRenterPoints;
end;
Rentals.Free;
end;
function TCustomer.Statement: string;
var
Rentals: TEnumeration;
each: TRental;
begin
Result := 'Rental Record for ' + self.Name + CRLF;
//---
Rentals := TEnumeration.Create(FRentals);
while Rentals.HasMoreElements do
begin
each := TRental(Rentals.NextElement);
//---显示此笔租借数据
Result := Result + each.Movie.Title + ' ' + FloatToStr(each.GetCharge) + CRLF;
end;
Rentals.Free;
//---结尾打印
Result := Result + 'Amount owed is ' + FloatToStr(self.GetTotalCharge) + CRLF;
Result := Result + 'You earned ' + IntToStr(self.GetTotalFrequentRenterPoints) + ' frequent renter points';
end;
function TCustomer.HtmlStatement: string;
var
Rentals: TEnumeration;
each: TRental;
begin
Result := '
Rental for ' + self.Name + '
' + CRLF;
//---
Rentals := TEnumeration.Create(FRentals);
while Rentals.HasMoreElements do
begin
each := TRental(Rentals.NextElement);
//---显示此笔租借数据
Result := Result + each.Movie.Title + ':' + FloatToStr(each.GetCharge) + '
' + CRLF;
end;
Rentals.Free;
//---结尾打印
Result := Result + '
You owe ' + FloatToStr(self.GetTotalCharge) + '
' + CRLF;
Result := Result + 'On this Rental you earned ' + IntToStr(self.GetTotalFrequentRenterPoints) + ' frequent renter points
';
end;
constructor TEnumeration.Create(const AList: TObjectList);
begin
FList := AList;
FIndex := 0;
end;
function TEnumeration.HasMoreElements: boolean;
begin
Result := FIndex < FList.Count;
end;
function TEnumeration.NextElement: TObject;
begin
Result := FList[FIndex];
FIndex := FIndex + 1;
end;
function TChildrensPrice.GetCharge(DaysRented: integer): double;
{计算一笔租片费用}
begin
Result := 1.5;
if DaysRented > 3 then
Result := Result + (DaysRented - 3) * 1.5;
end;
function TChildrensPrice.GetPriceCode: integer;
begin
Result := CHILDRENS;
end;
function TNewReleasePrice.GetCharge(DaysRented: integer): double;
{计算一笔租片费用}
begin
Result := DaysRented * 3;
end;
function TNewReleasePrice.GetFrequentRenterPoints(DaysRented: integer): integer;
{获取常客积点}
begin
if (DaysRented > 1) then
Result := 2
else
Result := 1;
end;
function TNewReleasePrice.GetPriceCode: integer;
begin
Result := NEW_RELEASE;
end;
function TRegularPrice.GetCharge(DaysRented: integer): double;
{计算一笔租片费用}
begin
Result := 2;
if DaysRented > 2 then
Result := Result + (DaysRented - 2) * 1.5;
end;
function TRegularPrice.GetPriceCode: integer;
begin
Result := REGULAR;
end;
function TPrice.GetFrequentRenterPoints(DaysRented: integer): integer;
{获取常客积点}
begin
Result := 1;
end;
end.