Template Method Design Pattern in Delphi. A working example

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.
Template Pattern Example (Delphi) – Country House
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:
Template Pattern Example (Delphi) – City House
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:
Template Pattern Example (Delphi) – Class diagram
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:

[1] I am borrowing the ASCII designs from Asciiworld.com : House.
[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.
[3] Get the full source code of this example here. If you liked it, please, consider to make a small donation to help the children of the world. You can make your tax deductible donation to UNICEF by clicking here. Remember: "Children are the hope of the world", José Martí.
[4] Books on Design Patterns:
Design Patterns: Elements of Reusable Object-Oriented Software

Head First Design Patterns

Object Models: Strategies, Patterns, and Applications (2nd Edition)

[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.

No comments:

Post a Comment