The Observer Pattern

Posted by on in Blogs

Introduction

When we use Delphi to design forms and data modules, every time we place a component on the designer, several things change: the form now shows an appropriate representation of the component, the object inspector changes to show the properties of the component and, if we press Alt-F12, we see the .DFM file now contains information about the component. Whether this behaviour was modelled correctly on the Observer pattern or not, the result is that several 'interested parties' got to know about a change in the content of the project that we are working on.

If you read the GoF Design Patterns book, you will find much discussion on the semantics and structure of the Observer pattern. Do we want to have one subject and many observers; do we want to have many subjects and one observer; or do we want many observers to keep track of many subjects? The answer to these questions will depend to a great extent on the application that you find yourself developing. Delphi, for example may be seen as using all of these variants in some part of the IDE.

In the same book, you will see that in order to implement the Observer pattern to implement a Digital Clock observer that relates to a Clock Timer subject, use is made of multiple inheritance. But Delphi does not support multiple inheritance... "No, not that old chestnut again!", I hear you cry, "Surely what we need in Delphi v10 is multiple inheritance?". Well, yes and no.

There are two primary mechanisms for circumventing a lack of multiple inheritance: Composition and Interfaces.

Abstract Concepts

Let us start by looking at the abstract concepts of Observers and Subjects as discussed in Gamma's book:


IObserver
procedure Update(Subject: IInterface);

ISubject
procedure Attach(Observer: IObserver);
procedure Detach(Observer: IObserver);
procedure Notify;


As you can see the basic idea of an Observer is that it can be told when the Subject has changed; this is achieved when the Subject calls the Observer.Update method and passes itself as the Subject parameter. The ISubject consists of methods for attaching and detaching IObservers as well as a Notify method, which iterates through any Observers that are attached.

Composition


If you are not comfortable with using interfaces, which is the simplest way of implementing the Observer pattern, then you need to use Composition to supply the necessary additional functionality to existing classes. Composition involves the placing of an instance of a class that implements a desired behaviour inside a derivative of the class that needs to be extended.


TObserver = class
public
procedure Update(const Subject: TObject); virtual; abstract;
end;

TSubject = class
private
fController: TObject;
fObservers: TObjectList;
public
constructor Create(const Controller: TObject);
procedure Attach(const Observer: TObserver);
procedure Detach(const Observer: TObserver);
procedure Notify;
end;


We start off by writing an abstract class for the Observer that provides a method called Update that can be overridden, depending on the class that is to be an Observer. The Subject class can take care of managing the list of Observers and the broadcasting of updates to them.


constructor TSubject.Create(const Controller: TObject);
begin
inherited Create;
fController := Controller;
end;

procedure TSubject.Attach(const Observer: TObserver);
begin
if fObservers = nil then
fObservers := TObjectList.Create;
if fObservers.IndexOf(Observer) < 0 then
fObservers.Add(Observer);
end;

procedure TSubject.Detach(const Observer: TObserver);
begin
if fObservers <> nil then
begin
fObservers.Remove(Observer);
if fObservers.Count = 0 then
begin
fObservers.Free;
fObservers := nil;
end;
end;
end;

procedure TSubject.Notify;
var
i: Integer;
begin
if fObservers <> nil then
for i := 0 to Pred(fObservers.Count) do
TObserver(fObservers[i]).Update(fController);
end;


The constructor for the TSubject class takes a TObject as a 'Controller' parameter and this object is retained for use as the 'real' Subject to be sent to each of the Observers; otherwise all the Observers will see is a TSubject and not the actual subject class.

Watching the Clock


The GoF book uses the example of a digital clock to demonstrate the principles of the Observer pattern and we will use that same example here.

Let's start by designing a simple class to represent the Clock mechanism:


TClockTimer = class
private
fTimer: TTimer;
fInternalTime: TDateTime;
fSubject: TSubject;
procedure Tick(Sender: TObject);
public
constructor Create;
destructor Destroy; override;
function GetTime: TDateTime;
property Subject: TSubject
read fSubject;
end;


This particular clock uses a TTimer to keep its own time and for the purpose of this example will update itself every second.

The signature of the Tick method is that of a TNotifyEvent in order to simplify the handling of the timer interval. In the Tick Method, I set an internal variable to the current time to avoid any difference in time portrayed between calls to GetTime by the attached Observers.


constructor TClockTimer.Create;
begin
inherited Create;
fTimer := TTimer.Create(nil);
fTimer.Interval := 1000;
fTimer.OnTimer := Tick;
fTimer.Enabled := True;
fSubject := TSubject.Create(self);
end;

destructor TClockTimer.Destroy;
begin
fSubject.Free;
fTimer.Enabled := False;
fTimer.Free;
inherited Destroy;
end;

function TClockTimer.GetTime: TDateTime;
begin
Result := fInternalTime;
end;

procedure TClockTimer.Tick(Sender: TObject);
begin
fInternalTime := Now;
fSubject.Notify;
end;


Notice the inclusion of a private TSubject field that will allow us to notify the list of Observers. The constructor not only creates the instance of TSubject, it also passes itself to the Subject constructor, so that the Subject can have a TClockTimer to pass to the Observers during the Notify method.

Every time the TTimer.OnTimer event fires, the internal time field is updated to the current time and then the Subject's Notify event is called.

Putting on a Face


Now we have a clock mechanism, we also need a face for our clock; a way of displaying the time provided by the mechanism.


TDigitalClock = class;

TClockObserver = class(TObserver)
private
fDisplay: TDigitalClock;
public
constructor Create(const Display: TDigitalClock);
procedure Update(const Subject: TObject); override;
end;

TDigitalClock = class(TPanel)
private
fObserver: TClockObserver;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
property Observer: TClockObserver
read fObserver;
procedure ObserverUpdate(const Subject: TClockTimer);
end;


As we have said before, Delphi does not support multiple inheritance and so, to be able to derive our Digital Clock face from TPanel we also need to use composition to mix a TObserver class with the TDigitalClock class. Note also that we have to ensure that the Update method of TPanel is not suitable for responding to calls to TObserver.Update, therefore we have called our method ObserverUpdate to avoid confusion.

Just as we had to pass in the Clock Timer to the Subject, we also have to pass the Digital Clock to the Observer. The Update method of TClockObserver will call ObserverUpdate in TDigitalClock to allow the Text property of the TPanel to be updated.


constructor TClockObserver.Create(const Display: TDigitalClock);
begin
inherited Create;
fDisplay := Display;
end;

procedure TClockObserver.Update(const Subject: TObject);
begin
if (Subject is TClockTimer) then
fDisplay.ObserverUpdate(TClockTimer(Subject));
end;


The Clock Observer class derives from TObserver and overrides the Update method to check if the Subject being passed is indeed a TClockTimer and then passes that Clock Timer to the ObserverUpdate method of the Digital Clock.


constructor TDigitalClock.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
fObserver := TClockObserver.Create(self);
end;

destructor TDigitalClock.Destroy;
begin
fObserver.Free;
inherited Destroy;
end;

procedure TDigitalClock.ObserverUpdate(const Subject: TClockTimer);
begin
Text := FormatDateTime('tt', Subject.GetTime);
end;


All that is left for the display class to do is to respond to the update by setting the Text property to the value provided by the Clock Timer subject.

Observer Interfaces


Instead of using Composition to circumvent the lack of multiple inheritance, we can also use Interfaces in a way that allows us to support the concept of deriving a class that 'inherits' the behaviour of more than one type.

When describing multiple inheritance, the example of an amphibious vehicle is often used. But the concept of an amphibious vehicle does not truly represent an object that is truly a car and truly a boat; surely it is, more accurately, a vehicle that can behave like a car or like a boat. What interfaces allow us to do is to design classes that support multiple behaviours. So let us go on to look at how we can simplify the Observer pattern using interfaces.


IObserver = interface
['{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}']
procedure Update(Subject: IInterface);
end;

ISubject = interface
['{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}']
procedure Attach(Observer: IObserver);
procedure Detach(Observer: IObserver);
procedure Notify;
end;


In the above example using Composition, we had to write an abstract class called TObserver that had just the one method, Update, but if you look at these interface declarations, you actually have the equivalent of an abstract class. Essentially an interface is almost the same as an abstract class with a few more features like reference counting and the ability to be mixed with other interfaces in a class.

As with the non-interface method of implementing the Observer pattern, we can implement the ISubject interface once and for all and aggregate an instance of TSubject into our Subject class to avoid rewriting the same code over and over again.


TSubject = class(TInterfacedObject, ISubject)
private
fController: Pointer;
fObservers: IInterfaceList;
procedure Attach(Observer: IObserver);
procedure Detach(Observer: IObserver);
procedure Notify;
public
constructor Create(const Controller: IInterface);
end;


Gamma uses a template List container class to maintain the list of Observers, but as we are not using C++ we will use a IInterfaceList as this is the correct way to store lists of references to interfaces. So let's go on to look at the implementation for this base TSubject class:


constructor TSubject.Create(const Controller: IInterface);
begin
inherited Create;
fController := Pointer(Controller);
end;

procedure TSubject.Attach(AObserver: IObserver);
begin
if fObservers = nil then
fObservers := TInterfaceList.Create;
fObservers.Add(AObserver);
Notify;
end;

procedure TSubject.Detach(AObserver: IObserver);
begin
if fObservers <> nil then
begin
fObservers.Remove(AObserver);
if fObservers.Count = 0 then
fObservers := nil;
end;
end;

procedure TSubject.Notify;
var
i: Integer;
begin
if fObservers <> nil then
for i := 0 to Pred(fObservers.Count) do
(fObservers[i]. as IObserver).Update(IInterface (fController));
end;


The constructor takes an IInterface reference to the aggregating object (in our example the Clock Timer) and stores it in a Pointer field; this 'weak reference' technique avoids reference-counting problems that could cause a memory leak due to the mutual references between the TSubject and its aggregating class.

The Attach and Detach methods are fairly straightforward, but I will go into a little more detail with the Notify method. This method traverses the list of Observers that are attached to the Subject and calls the Update method for each observer that it finds. The fController field that is the real subject (Clock Timer) has to be cast back to an IInterface in order to be passed to the Update method of the Observer interface.

A Universal Ticker


Here is the interface definition for the 'mechanism' of our clock:


IClockTimer = interface
['{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}']
function GetTime: TDateTime;
end;


The declaration of the TClockTimer implementing class is slightly different from the non-interface version. Instead of deriving from TObject, it derives from TInterfacedObject in order to give us a ready-made implementation of IInterface and then also implements the Clock Timer and Subject interfaces.


TClockTimer = class(TInterfacedObject, IClockTimer, ISubject)
private
fTimer: TTimer;
fInternalTime: TDateTime;
fSubject: ISubject;
function GetTime: TDateTime;
procedure Tick(Sender: TObject);
property Subject: ISubject
read fSubject
implements ISubject;
public
constructor Create;
end;


The main differences are: all methods (apart from the constructor) and properties are now private because they will only be accessed through the supported Interfaces. The Subject property is declared with the implements directive so that any attempt to reference the ISubject interface will be redirected to the embedded TSubject instance.


destructor TClockTimer.Destroy;
begin
fTimer.Enabled := False;
fTimer.Free;
inherited Destroy;
end;


The only difference in the code required for the interface version of this class is the omission of the call to fSubject. Free; this is not necessary or possible, as it will automatically fall out of scope when the Clock Timer is destroyed and Free is not a method of IInterface.

Widgets and Other Furry Animals


In Gamma's book, the Digital Clock class is derived from a Widget class and from the abstract Observer class previously discussed. As we are not able to use multiple inheritance, we are going to have to find another way of implementing a Widget that is also an Observer. For a very simple demonstration component that you can put on a form, I decided that my 'Widget' would be a TPanel. Here is the class declaration:


TDigitalClock = class(TPanel, IObserver)
private
procedure IObserver.Update = ObserverUpdate;
procedure ObserverUpdate(const Subject: IInterface);
end;

procedure TDigitalClock.ObserverUpdate(const Subject: IInterface);
var
Obj: IClockTimer;
begin
Subject.QueryInterface(IClockTimer, Obj);
if Obj <> nil then
Caption := FormatDateTime('tt', Obj.GetTime);
end;


Because TPanel already has an Update method that is unsuitable for our purposes, we have to redirect the IObserver.Update method to another method, which I have called ObserverUpdate.

In ObserverUpdate you will see that a check is made to ensure that the Subject being passed in is really a Clock Timer and then, if it is a valid subject, the visual representation on the 'Widget' is updated using the GetTime method of the Subject.

After installing this component into the VCL, the only other code needed to get a demonstration going is to declare a private variable of type IClockTimer on a test form then add a button and the following event handlers:


procedure TForm1.FormCreate(Sender: TObject);
begin
fClockTimer := TClockTimer.Create;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
(DigitalClock1 as ISubject).Attach(fClockTimer as IObserver);
end;


Any number of Digital Clocks can be placed on the form and attached to the Clock Timer and they will all be notified and kept up to date every second.

Comments are not available for public users. Please login first to view / add comments.