Title: Simple Observer pattern
Question: How to make objects aware of changes in the state of another object, without coupling the changing object with the observing object.
Answer:
To make, for example a form, avare of changes in another object we can
add a notify event to that object and implement some method to it. But
in that case only one observer can exist for that class. If more than
one class has to get notified of changes in the class, we have to implement some kind of list with events. This is best implemented with an Observer-pattern.
And here we have our little version of the pattern :).
The unit Observer_un consists of two classes
TObserverItem wich is the container for one "observation" event
TObserverMgr Maintains a list of TObserverItem and has a method for
running registered observers
For clarity we call the classes thats going to use these for observation
TObserver : The class that want to get notified of changes
TChanger : The class that changes
To use this you start with the class you want others to be able to
observe (TChanger), and implement a private field for TObserverMgr.
Then to avoid need for coupling between observer and TObserverMgr you
implement two methods, AddObserver and RemoveObserver in the changer
which just forwards the methods to TObserverMgr.
procedure TChanger.AddObserver(aNotifyEvent: TNotifyEvent; Observer: TObject);
begin
// Forwarding method call, Observer should not need to know about fObserverMgr
fObserverMgr.AddObserver(aNotifyEvent, Observer);
end;
Now if you want to observe the field FMyString in changer you implement
a property MyString with a set method
Could look like this
procedure TChanger.SetMyStr( value : String )
begin
FMyString := Value ;
// And now we want to call all the observers
FObserverMgr.RunObservers ;
end;
Ok its esier for you to understand the source so look below or in Observer.zip
which has an example also.
/Christer & Fredrik
unit Observers_un;
interface
uses
contnrs, classes;
type
{{
Item for holding an observer and one of its TNotifyEvents.
}
TObserverItem = class
private
FObserved: TObject;
FObserver: TObject;
FOnChange: TNotifyEvent;
protected
constructor Create(theObserved: TObject);
public
procedure Change;
property Observer: TObject read FObserver write FObserver;
property OnChange: TNotifyEvent read FOnChange write FOnChange;
end;
{{
Class for managing ObserverItems.
Holds methods for inserting deleting and running
ObserverItems.
}
TObserverMgr = class (TObject)
private
FObserved: TObject;
FObserverList: TObjectList;
function FindMatch(aNotifyEvent: TNotifyEvent; Observer: TObject): Integer;
public
constructor Create(Owner : TObject);
procedure AddObserver(aNotifyEvent : TNotifyEvent ; Observer : TObject);
procedure RemoveObserver(aNotifyEvent : TNotifyEvent ; Observer : TObject);
procedure RunObservers;
end;
implementation
{
****************************************** TObserverItem *******************************************
}
constructor TObserverItem.Create(theObserved: TObject);
begin
inherited Create ;
FObserved := theObserved ;
end;
procedure TObserverItem.Change;
begin
if Assigned(FOnChange) then
FOnChange( FObserved ) ;
end;
{
******************************************* TObserverMgr *******************************************
}
constructor TObserverMgr.Create(Owner : TObject);
begin
inherited Create ;
FObserved := Owner ;
FObserverList := TObjectList.Create;
FObserverList.OwnsObjects := true;
end;
procedure TObserverMgr.AddObserver(aNotifyEvent : TNotifyEvent ; Observer : TObject);
var
anItem: TObserverItem;
begin
if (0 FindMatch(aNotifyEvent, Observer)) then
begin
anItem := TObserverItem.Create(FObserved) ;
anItem.OnChange := aNotifyEvent ;
anItem.Observer := Observer ;
FObserverList.Add(anItem) ;
end ;
end;
function TObserverMgr.FindMatch(aNotifyEvent: TNotifyEvent; Observer: TObject): Integer;
var
I: Integer;
anItem: TObserverItem;
// Check if this Observer and notify event exists in FObserverList
// Result is -1 if not present, else it's the index in FObserverList
//
// If aNotifyEvent is Nil then the first item with the same observer will
// return it's index
//
begin
result := -1 ;
For I := 0 to FObserverList.count-1 do
begin
anItem := TObserverItem( FObserverList.Items[i] ) ;
if anItem.Observer = Observer then
begin
if @aNotifyEvent = nil then
begin
result := I ;
exit ;
end
else if (@anItem.OnChange = @aNotifyEvent) then
begin
result := I ;
exit ;
end ;
end ;
end ;
end;
procedure TObserverMgr.RemoveObserver(aNotifyEvent : TNotifyEvent ; Observer : TObject);
var
I: Integer;
begin
// If aNotifyEvent is Nil then we loop through the list
// and removes all items with this observer
repeat
I := FindMatch(aNotifyEvent, Observer) ;
if (I = 0) then FObserverList.Delete(I) ;
until (I end;
procedure TObserverMgr.RunObservers;
var
I: Integer;
anObserver: TObserverItem;
begin
For I := 0 to FObserverList.Count - 1 do
begin
anObserver := TObserverItem( FObserverList.items[i] );
anObserver.Change ;
end ;
end;
end.