The Template Method Pattern is very easy to understand and implement. Here’s the definition borrowed from Design Patterns: Elements of Reusable Object-Oriented Software book:
“Define the skeleton of an algorithm in an operation, deferring some steps to subclasses. Template Method lets subclasses redefine certain steps of an algorithm without changing the algorithm's structure.”
Let’s try to understand the pattern walking through a simple example. For implementing the solution we’ll use Delphi XE, but it should work for previous versions as well.
This is the wording of the example:
Design an application that allows drawing different styles of houses (ei. country house, city house) using ASCII art [1].
In the following image there’s a prototype of the user interface.
The GUI is composed by a form, a memo [2], two buttons, a label and a combo box. Using the combo box you can choose whether your house will have a chimney or not. Clicking the buttons will result in an ASCII house printed out in the memo area. In the example above, the Draw Country House was clicked. If you click the other button, you will get city house like the one below:
What happens if you select “No” in the combo box? Try it and let me know :-) Here’s the link to the EXE. This EXE is harmless; I am not a bad guy: no Trojans, no viruses, just Delphi :-)
Now, what classes do we need to make this work? Take a look at the following class diagram and try to make sense of it:
We have an abstract superclass THouse, which contains four abstract (pure virtual) methods: BuildFloor, BuildWalls, BuildRoof and BuildChimney. These methods are implemented in the descending classes TCountryHouse and TCityHouse. Pay special attention to the method BuildIt in THouse. This method is an instance method that is inherited by both concrete subclasses: TCountryHouse and TCityHouse.
I want to be more detailed:
The BuildIt method is implemented just once in the class THouse. The subclasses TCountryHouse and TCityHouse don’t have to implement the BuildIt method.
The BuildFloor, BuildWalls, BuildRoof and BuildChimney methods have no implementation under the THouse class. The TCountryHouse has one implementation of these methods, while TCityHouse have a different implementation of them.
This is how the BuildIt method looks like:
function THouse.BuildIt: string;
begin
Result:= BuildRoof + //Step 1
BuildWalls + //Step 2
BuildFloor; //Step 3
if FHasChimney then
Result:= BuildChimney+ Result; //Step 4 (conditional)
end;
This method defines a general algorithm to build a house…it doesn’t take into consideration the kind of house that’s been built. Notice that the steps for building the roof, walls, floor and chimney (if any) have been deferred to the subclasses. The BuildIt method is known as a template method that gives the name to this pattern.
Let me give you an idea of the implementation of the BuildFloor method. This method is implemented directly by the subclasses. Take a look:
function TCountryHouse.BuildFloor: string;
begin
Result:=
'~~~~~" "~~~~~~~~~~~~~~~~~~~~~~~~ ';
end;
function TCityHouse.BuildFloor: string;
begin
Result:=
'******************____****************'#13#10 +
'**************************************';
end;
Notice that the implementation of the BuildFloor method is different in both subclasses TCountryHouse and TCityHouse.
For the implementation of the remaining methods refer to reference [5]. For the full source code refer to [3].
In conclusion, the template method pattern needs an abstract superclass to implement a template method (common for all subclasses). The steps within this template method are deferred into methods that are implemented by the concrete subclasses. Did you get it? :-)
Do you want to know more about design patterns? The books in reference [4] are just what you need.
References:
[2] Make sure to use a fixed-width font (Courier, Monaco, Courier New, Lucida Console, etc.) for the memo, otherwise the drawing will look fuzzy.
[4] Books on Design Patterns:
[5] The full source code of the unit:
unit TemplatePatternExample;
interface
type
THouse = class
private
FHasChimney: Boolean;
public
constructor Create;
property HasChimney: Boolean read FHasChimney
write FHasChimney;
function BuildFloor: string; virtual; abstract;
function BuildWalls: string; virtual; abstract;
function BuildRoof: string; virtual; abstract;
function BuildChimney: string; virtual; abstract;
function BuildIt: string;
end;
TCountryHouse = class(THouse)
public
function BuildFloor: string; override;
function BuildWalls: string; override;
function BuildRoof: string; override;
function BuildChimney: string; override;
end;
TCityHouse = class(THouse)
public
function BuildFloor: string; override;
function BuildWalls: string; override;
function BuildRoof: string; override;
function BuildChimney: string; override;
end;
implementation
{ THouse }
constructor THouse.Create;
begin
inherited Create;
FHasChimney:= True;
end;
function THouse.BuildIt: string;
begin
Result:= BuildRoof + //Step 1
BuildWalls + //Step 2
BuildFloor; //Step 3
if FHasChimney then
Result:= BuildChimney+ Result; //Step 4 (conditional)
end;
{ TCountryHouse }
function TCountryHouse.BuildChimney: string;
begin
Result:=
' ( ) '#13#10 +
' ( ) '#13#10 +
' ( ) '#13#10 +
' ( ) '#13#10 +
' ) ) '#13#10 +
' ( ( '#13#10 +
' (_) '#13#10 +
' [ ] '#13#10;
end;
function TCountryHouse.BuildRoof: string;
begin
Result:=
' ___________________ '#13#10 +
' /\ ______ \ '#13#10 +
' //_\ \ /\ \ '#13#10 +
' //___\ \__/ \ \ '#13#10 +
' //_____\ \ |[]| \ '#13#10 +
' //_______\ \|__| \ '#13#10 +
' /XXXXXXXXXX\ \ '#13#10 +
'/_I_II I__I_\__________________\'#13#10;
end;
function TCountryHouse.BuildWalls: string;
begin
Result:=
' I_I| I__I_____[]_|_[]_____I'#13#10 +
' I_II I__I_____[]_|_[]_____I'#13#10 +
' I II__I I XXXXXXX I'#13#10;
end;
function TCountryHouse.BuildFloor: string;
begin
Result:=
'~~~~~" "~~~~~~~~~~~~~~~~~~~~~~~~ ';
end;
{ TCityHouse }
function TCityHouse.BuildChimney: string;
begin
Result:=
' ==== '#13#10 +
' !!!! '#13#10;
end;
function TCityHouse.BuildRoof: string;
begin
Result:=
' ========================== '#13#10 +
' %%%%%%%%%%%%%%%%%%%%%%%%%%%%%% '#13#10 +
' %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% '#13#10 +
'%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%'#13#10;
end;
function TCityHouse.BuildWalls: string;
begin
Result:=
' || _____ _____ ||'#13#10 +
' || | | | | | | ||'#13#10 +
' || |-|-| |-|-| ||'#13#10 +
' || ##### ##### ||'#13#10 +
' || ||'#13#10 +
' || _____ ____ _____ ||'#13#10 +
' || | | | @@@@ | | | ||'#13#10 +
' || |-|-| @@@@ |-|-| ||'#13#10 +
' || ##### @@*@ ##### ||'#13#10 +
' || @@@@ ||'#13#10;
end;
function TCityHouse.BuildFloor: string;
begin
Result:=
'******************____****************'#13#10 +
'**************************************';
end;
end.