Abstract Factory pattern

Origin

This abstract factory pattern is based on that described in [Gam+, pages 87..95]. The Delphi implementation for the pattern is from Shaun Parry.

Intent

'To provide an interface for creating families of related or dependent objects without specifying their concrete classes' [Gam+,p87].

Motivation

The motivation is based on [Gam+, page 87]. Consider a composite class which must contain the same members but be created differently depending on the application. For example, you could have a document which would be printed differently depending on the printer being used, but would still have to contain the same information. The idea behind abstract factory is that you can pass a class (the factory) to the constructor which has a standard interface. This interface is then used by the constructor, but the methods it uses are implemented differently for each kind of factory.
 
 
In the above class diagram, you can see how the Client Class function, CreateProduct, uses the AbstractFactory classes' CreateProduct function to create an object of the AbstractProduct class. Because there is no direct connection between the Client and the ConcreteProduct class, the ConcreteProduct can be any derived class of AbstractProduct.

Implementation

The following is an example of making a panel with three edit boxes within it. To do this we are goint to use a method called CreatePanel within a class we will create for the job called PanelMaker:

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

implementation

function TPanelMaker.CreatePanel(aOwner : TWinControl;
                                 Factory : TAbstractFactory): TPanel;
var
  TempPanel : TPanel;
  TempEdit1 : TEdit;
  TempEdit2 : TEdit;
  TempEdit3 : TEdit;
begin
  TempPanel := Factory.MakePanel( aOwner );
  TempEdit1 := Factory.MakeEdit( TempPanel, 'Test1', 10, 10 );
  TempEdit2 := Factory.MakeEdit( TempPanel, 'Test2',
                                 TempEdit1.Top + TempEdit1.Height + 10,
                                 TempEdit1.Left );
  TempEdit3 := Factory.MakeEdit( TempPanel, 'Test3',
                                 TempEdit2.Top + TempEdit2.Height + 10,
                                 TempEdit2.Left );
  CreatePanel := TempPanel;
end;

Notice that we are not using TPanel.Create directly, but using the abstract factory method MakePanel instead. This makes it easier to change.

The abstract factory looks like this:

type
  TAbstractFactory = class (TObject)
  protected
    FPanel : TPanel;
  public
    function MakeEdit( aOwner : TWinControl;
                       vText : string;
                       vTop : Integer;
                       vLeft : Integer ): TEdit; virtual;
    function MakePanel( aOwner : TWinControl ) : TPanel; virtual;
  end;

implementation

function TAbstractFactory.MakeEdit( aOwner : TWinControl;
                                    vText : string;
                                    vTop : Integer;
                                    vLeft : Integer ): TEdit;
var
  TempEdit : TEdit;
begin
  TempEdit := TEdit.Create( aOwner );
  TempEdit.Parent := aOwner;
  TempEdit.Text := vText;
  TempEdit.Top := vTop;
  TempEdit.Left := vLeft;
  MakeEdit := TempEdit;
end;

function TAbstractFactory.MakePanel( aOwner : TWinControl ) : TPanel;
var
  TempPanel : TPanel;
begin
  TempPanel := TPanel.Create( aOwner );
  TempPanel.Parent := aOwner;
  MakePanel := TempPanel;
end;

This is for standard edit boxes and panels, but say we want to have a panel with a special type of edit box which only has, say, uppercase characters in it.

type
  TUppercaseEdit = class (TEdit)
  protected
    FOnChange : TNotifyEvent;
    procedure Change; override;
    function GetUppercase : string;
    procedure SetUppercase( Value : string );
  public
    property Text : string read GetUppercase write SetUppercase;
  end;

You can then create a derivative of AbstractFactory which will use this edit box in place of the standard:

function TUppercaseFactory.MakeEdit( aOwner : TWinControl;
                                    vText : string;
                                    vTop : Integer;
                                    vLeft : Integer ): TEdit;
var
  TempEdit : TUppercaseEdit;
begin
  TempEdit := TUppercaseEdit.Create( aOwner );
  TempEdit.Parent := aOwner;
  TempEdit.Text := vText;
  TempEdit.Top := vTop;
  TempEdit.Left := vLeft;
  MakeEdit := TempEdit;
end;

Thus you don't have to change PanelMaker.CreatePanel.