Defines the skeleton of an algorithm in an operation, deferring some steps to subclasses without changing the algorithm's structure.
Good for
- encapsulating a policy that is deployed by many different agents
- reusing the code in a complex algorithm by encapsulating invariable parts and letting subclasses implement the behavior that can vary
- providing hook operations at specific points that a subclass can extend, but requiring to be callable through the template method on the base class.
Structure of the Template Pattern

Implementation of the Template Pattern
The Template pattern has several examples in the DelphiVCL, as might be expected in any object framework. In this sample, the VCL's TStream abstract class (classes.pas) implements stream copying in the template method CopyFrom(), which abstracts the algorithm for reading data from a stream and preparing its destination. TStream defers the implementation of Read() and Write() methods to its subclasses by declaring them as abstract methods. CopyFrom() uses Read() and Write() via the ReadBuffer() and WriteBuffer() methods, which are themselves template methods-since they are static and simply provide an interface to Read() and Write() virtual methods. Read() and Write() are the specific extension points that must be implemented in derived classes.
For implementation details, please review the VCL's classes.pas unit, the TStream abstract class, and the concrete stream classes: TCustomMemoryStream/TMemoryStream and TStringStream; or download Streams.hlp.
Since you want users of your class to override the specific extension points and not the interface provided by the template method, a template method is usually declared statically on the base class, and the primitive methods are virtual.
{ TStream abstract class }
TStream = class(TObject)
private
...
protected
procedure SetSize(NewSize: Longint); virtual;
public
// primitive operations to be provided by derived classes
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; virtual; abstract;
// template methods
procedure ReadBuffer(var Buffer; Count: Longint);
procedure WriteBuffer(const Buffer; Count: Longint);
// acts as template method by encapsulating the copy algorithm
function CopyFrom(Source: TStream; Count: Longint): Longint;
...
end;
// concrete class
TStringStream = class(TStream)
...
public
constructor Create(const AString: string);
// concrete class implementations - varying behavior
function Read(var Buffer; Count: Longint): Longint; override;
...
function Write(const Buffer; Count: Longint): Longint; override;
...
end;
{ TStream } // abstract class
...
// template methods
procedure TStream.ReadBuffer(var Buffer; Count: Longint);
begin
if (Count <> 0) and (Read(Buffer, Count) <> Count) then
raise EReadError.Create(SReadError);
end;
procedure TStream.WriteBuffer(const Buffer; Count: Longint);
begin
if (Count <> 0) and (Write(Buffer, Count) <> Count) then
raise EWriteError.Create(SWriteError);
end;
function TStream.CopyFrom(Source: TStream; Count: Longint): Longint;
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;
{ TStringStream } // concrete class
...;
// concrete class implementation of primitive operations
function TStringStream.Read(var Buffer; Count: Longint): Longint;
begin
Result := Length(FDataString) - FPosition;
if Result > Count then
Result := Count;
Move(PChar(@FDataString[FPosition + 1])^, Buffer, Result);
Inc(FPosition, Result);
end;
function TStringStream.Write(const Buffer; Count: Longint): Longint;
begin
Result := Count;
SetLength(FDataString, (FPosition + Result));
Move(Buffer, PChar(@FDataString[FPosition + 1])^, Result);
Inc(FPosition, Result);
end;
...