Wrapper Design Pattern

Intent

'A wrapper converts the interface of a class into another interface clients expect. Wrappers let classes work together that couldn't otherwise because of incompatible interfaces' [Gam+ 139].

Motivation

In Delphi's Object Pascal language polymorphism is based on class type rather than on the supported interfaces. This implies that although two classes can support the same interface, they must have the same ancestor in order to be polymorphically exchangeable for client object. Sometimes you want existing but unrelated classes to work together. The wrapper pattern lets you wrap (parts of) the interface of a class by an other class. This simulates multiple inheritance in Delphi by use of 'uses' relations.

You could for example have an existing class TSample inheriting from TObject which you want to add to Delphi's component palette. However, Components must descend from TComponent. Assuming that there is a good reason for not changing TSample's ancestor class to TComponent (for example you don't have the source), you could create a new class TSampleWrapper which inherits from TComponent and which 'uses' or 'consists of' a TSample. Since the TSampleWrapper is a TComponent descendant it can be added to the component palette. You could now 'wrap' the interface of TSample and make it available in the TSampleWrapper class. The TSampleWrapper delegates the actual behaviour to it's wrapped Sample. It does this by calling the appropriate method in Sample or access the appropriate property in Sample.

Another reason to use a wrapper pattern is that it helps to stick to the 'Law of Demeter'. This law basically tells you not to reference objects more than one level deep. Assume the interfaces of the classes TSample and TSampleWrapper to be like:

type
   TSample = class (TObject)
   private
     FSomeValue: Integer;
   public
     function SomeAction(const Data: string): Boolean;
     property SomeValue: Integer read FSomeValue write FSomeValue;
   end;

   TSampleWrapper = class(TComponent)
   private
     FSample: TSample;
   public
     property Sample: TSample read FSample;
   end;

According to Demeter's law calling SampleWrapper.Sample is fine, but SampleWrapper.Sample.SomeAction is not good practice. It would be better todefine a SomeAction method in TSampleWrapper which calls the Sample.SomeAction method.

Actually, code like ListBox.Canvas.Brush.Color violates this law, since we reference three levels deep.

Implementation

We'll use the above described classes to demonstrate the implementation of a wrapper. In the example TSampleWrapper 'consists of' a TSample which is referenced in the Sample property. The ModelMaker wrapper pattern can now make the method SomeAction and property SomeValue available to the TSampleWrapper interface and fully implement the wrapped members.

  TSampleWrapper = class (TComponent)
  private
     FSample: TSample;
  protected
     function GetSomeValue: Integer;
     procedure SetSomeValue(Value: Integer);
  public
     function SomeAction(const Data: string): Boolean;
     property Sample: TSample read FSample;
     property SomeValue: Integer read GetSomeValue write SetSomeValue;
  end;

This allows users of TSampleWrapper for example to access the SomeAction method immediately without referencing the SampleWrapper.Sample.SomeAction. The implementation for this interface will be: (note that this is fully implemented, and ready to compile)

function TSampleWrapper.GetSomeValue: Integer;
begin
  Result := Sample.SomeValue;
end;

procedure TSampleWrapper.SetSomeValue(Value: Integer);
begin
  Sample.SomeValue := Value;
end;

function TSampleWrapper.SomeAction(const Data: string): Boolean;
begin
  Result := Sample.SomeAction(Data);
end;

In this example you see some of the wrapper's functionality. In general: