用Delphi实现模板模式(Template Method模式)

作者 陈省

 

考虑现在需要写一个全自动洗衣机的洗衣过程模拟程序,考虑到不同品牌的洗衣机的实现差异很大的,所以先建立一个洗衣的基类,定义了纯虚的洗衣方法,不同品牌的洗衣机分别重载洗衣的方法来实现洗衣的过程,定义的模型如下:

 

 

其中THaierWasher和TCygnetWasher分别对应海尔和小天鹅的洗衣机。进一步分析洗衣过程,会发现洗衣其实是由加水,搅拌,放水,甩干等固定的步骤按固定的顺序构成的。每一台洗衣机不论品牌都是这样的步骤和顺序,有区别是放水时,各个洗衣机不同,搅拌时速度也会不同,放水时快慢有所不同,甩干时转速会不一样。也就是说洗衣的大的流程是比较固定的,但是在细节实现上是有区别的。

 

因为大的流程是一致的,因此应该将这些大的流程行为抽象到公共基类中以避免代码重复。修改后的类图示意如下:

 

 

修改后,Wash方法不再是虚方法,不允许被重载,这样就固定了洗衣的大的流程,而不同品牌的洗衣机则重载细节实现方法包括Water(加水),BeatUp(搅拌),OutFlow(排水), Dry(甩干)。

 

修改后的Wash方法就相当于模版方法,一次性的实现了洗衣算法的不变的部分,而将可变的部分如Dry, BeatUp等方法延迟到子类来实现,这种模式就叫模板模式。前面我们刚刚研究了策略模式,它的目的也是实现动态的算法,和模板方法的区别在于策略模式上使用对象组合的方式来实现的,而模版方法是使用类继承的方法来实现的。策略模式替换的是整个算法,而模板方法则是只改变算法的一部分,总体算法并不改变。

 

VCL中的模板方法

 

VCL中大量的使用了模板方法,最常见的就是TStream类了,TStream类的定义如下:

 

  TStream = class(TObject)
  private
    function GetPosition: Int64;
    procedure SetPosition(const Pos: Int64);
    procedure SetSize64(const NewSize: Int64);
  protected
    function GetSize: Int64; virtual;
    procedure SetSize(NewSize: Longint); overload; virtual;
    procedure SetSize(const NewSize: Int64); overload; virtual;
  public
    function Read(var Buffer; Count: Longint): Longint; virtual; abstract;
    function Write(const Buffer; Count: Longint): Longint; virtual; abstract;
    function Seek(Offset: Longint; Origin: Word): Longint; overload; virtual;
    function Seek(const Offset: Int64; Origin: TSeekOrigin): Int64; overload; virtual;
    procedure ReadBuffer(var Buffer; Count: Longint);
    procedure WriteBuffer(const Buffer; Count: Longint);
    function CopyFrom(Source: TStream; Count: Int64): Int64;
    function ReadComponent(Instance: TComponent): TComponent;
    function ReadComponentRes(Instance: TComponent): TComponent;
    procedure WriteComponent(Instance: TComponent);
    procedure WriteComponentRes(const ResName: string; Instance: TComponent);
    procedure WriteDescendent(Instance, Ancestor: TComponent);
    procedure WriteDescendentRes(const ResName: string; Instance, Ancestor: TComponent);
    procedure WriteResourceHeader(const ResName: string; out FixupInfo: Integer);
    procedure FixupResourceHeader(FixupInfo: Integer);
    procedure ReadResHeader;
    property Position: Int64 read GetPosition write SetPosition;
    property Size: Int64 read GetSize write SetSize64;
  end;

 

其中Read,Write是根据不同的流类必须重载的方法,因此定义为Abstract方法,而Seek方法对于不同的类如THandleStream可能也需要重载,但是TStream类给了一个一般意义的默认实现,对于这类需要重载的方法,一般称其为钩子方法。而CopyFrom,WriteComponent等方法的算法都比较固定,因此在基类中算法都已经被实现了,这些方法都是模版方法。比如下面就是CopyFrom方法的算法,它调用ReadBuffer和WriteBuffer来完成流数据的复制:

 

function TStream.CopyFrom(Source: TStream; Count: Int64): Int64;
const
  MaxBufSize = $F000;
var
  BufSize, N: Integer;
  Buffer: PChar;
begin
  if Count = 0 then
  begin
    Source.Position := 0;
    Count := Source.Size;
  end;
  Result := Count;
  if Count > MaxBufSize then BufSize := MaxBufSize else BufSize := Count;
  GetMem(Buffer, BufSize);
  try
    while Count <> 0 do
    begin
      if Count > BufSize then N := BufSize else N := Count;
      Source.ReadBuffer(Buffer^, N);
      WriteBuffer(Buffer^, N);
      Dec(Count, N);
    end;
  finally
    FreeMem(Buffer, BufSize);
  end;
end;

 

其中ReadBuffer和WriteBuffer方法也是模板方法,其实是调用Read和Write虚方法来完成的,Read和Write方法被延迟到了子类来实现:

 

procedure TStream.ReadBuffer(var Buffer; Count: Longint);
begin
  if (Count <> 0) and (Read(Buffer, Count) <> Count) then
    raise EReadError.CreateRes(@SReadError);
end;
 
procedure TStream.WriteBuffer(const Buffer; Count: Longint);
begin
  if (Count <> 0) and (Write(Buffer, Count) <> Count) then
    raise EWriteError.CreateRes(@SWriteError);
end;

 

下面就是THandleStream对钩子方法的实现:

 

function THandleStream.Read(var Buffer; Count: Longint): Longint;
begin
  Result := FileRead(FHandle, Buffer, Count);
  if Result = -1 then Result := 0;
end;
 
function THandleStream.Write(const Buffer; Count: Longint): Longint;
begin
  Result := FileWrite(FHandle, Buffer, Count);
  if Result = -1 then Result := 0;
end;
 
function THandleStream.Seek(const Offset: Int64; Origin: TSeekOrigin): Int64;
begin
  Result := FileSeek(FHandle, Offset, Ord(Origin));
end;

 

除了TStream之外,我们常用的TStrings以及TCustomIniFile等类也都是模板类。

 


本站原创及翻译内容保留版权,欢迎转贴,转贴时请注明转自Delphi深度探索