Builder pattern

Origin

This builder pattern is based on that described in [Gam+, pages 97..106]. The Delphi implementation for the pattern is from Shaun Parry.

Intent

'Define an interface for creating an object, but let subclasses decide which class to instantiate' [Gam+,p97].

Motivation

The motivation is based on [Gam+, page 97]. Builder has a similar motivation to the abstract factory but, whereas in that pattern, the client uses the abstract factory class methods to create its own object, in Builder the client instructs the builder class on how to create the object and then asks it for the result. How the class is put together is up to the Builder class. It's a subtle difference.

Implementation

The following is an example of making a panel with three edit boxes within it, as with the Abstract Factory pattern. Again we are going to use a method called CreatePanel within a class called PanelMaker:

type
  TPanelMaker = class (TObject)
  public
    function CreatePanel( aOwner : TWinControl; PanelBuilder : TBuilder ): TPanel;
  end;

implementation

function TPanelMaker.CreatePanel(aOwner : TWinControl; PanelBuilder : TBuilder ): TPanel;
// Notice that thereare no
// local variables such as
// TempPanel and TempButton
// These are kept local to the builder object
begin
  PanelBuilder.BuildPanel( aOwner, 100, 200 );
 
  PanelBuilder.BuildEdit( 10, 10, 21, 100 );
  PanelBuilder.BuildEdit( 40, 10, 21, 100 );
  PanelBuilder.BuildEdit( 70, 10, 21, 100 );
  CreatePanel := PanelBuilder.Panel;
end;

Notice that all we are doing is telling the builder what to do with the panel, rather than asking it for various components.

The builder class looks like this:

type
  TPanelBuilder = class (TObject)
  protected
    FButtonList : TList;
    FEditList : TList;
    FPanel: TPanel;
    function GetPanel: TPanel; virtual;
  public
    procedure BuildButton(Caption : string;
                          Top : Integer;
                          Left : Integer;
                          Height : Integer;
                          Width: Integer); virtual;
    procedure BuildEdit(Top : Integer;
                        Left : Integer;
                        Height : Integer;
                        Width : Integer); virtual;
    procedure BuildPanel(aOwner : TWinControl;
                         Height : Integer;
                         Width : Integer); virtual;
    property Panel: TPanel read GetPanel;
  end;

implementation

procedure TPanelBuilder.BuildButton(Caption : string;
                                    Top : Integer;
                                    Left : Integer;
                                    Height : Integer;
                                    Width: Integer);
var
  TempButton: TButton;
begin
  TempButton := TButton.Create( FPanel );
  TempButton.Parent := FPanel;
  TempButton.Caption := Caption;
  TempButton.Top := Top;
  TempButton.Left := Left;
  TempButton.Height := Height;
  TempButton.Width := Width;
end;

procedure TPanelBuilder.BuildEdit(Top : Integer;
                                  Left : Integer;
                                  Height : Integer;
                                  Width : Integer);
var
  TempEdit: TEdit;
begin
  TempEdit := TEdit.Create( FPanel );
  TempEdit.Parent := FPanel;
  TempEdit.Top := Top;
  TempEdit.Left := Left;
  TempEdit.Height := Height;
  TempEdit.Width := Width;
end;

procedure TPanelBuilder.BuildPanel(aOwner : TWinControl;
                                   Height : Integer;
                                   Width : Integer);
begin
  FPanel := TPanel.Create( aOwner );
  FPanel.Parent := aOwner;
  FPanel.Height := Height;
  Fpanel.Width := Width;
end;

function TPanelBuilder.GetPanel: TPanel;
begin
  GetPanel := FPanel;
end;
 

Say that we want to change this, such that we want to make the default panel one with OK and Cancel buttons. All we have to do is sub class TPanelBuilder and override BuildPanel, thus:

type
  TButtonPanelBuilder = class (TPanelBuilder)
  public
    procedure BuildPanel(aOwner : TWinControl;
                         Height : Integer;
                         Width : Integer); override;
  end;

procedure TButtonPanelBuilder.BuildPanel(aOwner : TWinControl;
                                         Height : Integer;
                                         Width : Integer);
var
  OKButton: TButton;
  CancelButton: TButton;
begin
  inherited BuildPanel(aOwner, Height, Width);
  FPanel.Width := FPanel.Width + 60; // increase the size of the
                                     // panel to fit the buttons
 
  OKButton := TButton.Create( FPanel );
  OKButton.Parent := FPanel;
  OKButton.Caption := 'OK';
  OKButton.Top := FPanel.Top + 21;
  OKButton.Left := FPanel.Width - 60;
  OKButton.Width := 50;
 
  CancelButton := TButton.Create( FPanel );
  CancelButton.Parent := FPanel;
  CancelButton.Caption := 'Cancel';
  CancelButton.Top := OKButton.Top + OKButton.Top + 21;
  CancelButton.Left := FPanel.Width - 60;
  CancelButton.Width := 50;
end;