Articles   Members Online:
-Article/Tip Search
-News Group Search over 21 Million news group articles.
-Delphi/Pascal
-CBuilder/C++
-C#Builder/C#
-JBuilder/Java
-Kylix
Member Area
-Home
-Account Center
-Top 10 NEW!!
-Submit Article/Tip
-Forums Upgraded!!
-My Articles
-Edit Information
-Login/Logout
-Become a Member
-Why sign up!
-Newsletter
-Chat Online!
-Indexes NEW!!
Employment
-Build your resume
-Find a job
-Post a job
-Resume Search
Contacts
-Contacts
-Feedbacks
-Link to us
-Privacy/Disclaimer
Embarcadero
Visit Embarcadero
Embarcadero Community
JEDI
Links
How can many objects be notified by an event Turn on/off line numbers in source code. Switch to Orginial background IDE or DSP color Comment or reply to this aritlce/tip for discussion. Bookmark this article to my favorite article(s). Print this article
23-Sep-03
Category
Algorithm
Language
Delphi 5.x
Views
170
User Rating
No Votes
# Votes
0
Replies
0
Publisher:
DSP, Administrator
Reference URL:
DKB
			Author: Jérôme Tremblay

How can many objects be notified by an event?

Answer:

Sometimes, there is a need to notify many different objects about a state change. 
My favorite solution to this problem is the Observer pattern, described in Design 
Patternshttp://www.amazon.com/exec/obidos/ASIN/0201633612/o/qid=992888249/sr=2-1/002
-7628578-2660038. (I highly recommend this book). Roughly, this patterns describe a 
way for objects that want to be notified of a change (called the Observers) to 
register themselves with the subject (called the Subject :). This subject then has 
the responsability to notify all it's observers when it's internal state changes. 

And this is where we encounter our first problem. How does the Subject notify it's 
Observers? By calling one of the Observer's method. But this means the Subject has 
to know the Observer somehow, not very flexible. We could use an abstract Observer 
class, in that way our Subject would always be able to call a known method, but 
this would force us to always descend observers from the same hiearchy, which is 
not very practical (and sometimes completely impossible). 

Fortunately, for each problem there is a solution, and in our case there are at 
least TWO! 

The first solution is to use Interfaces. Our Subject would accept an IObserver 
interface. The neat thing about interfaces is that any class can implement any 
interface, and as long as an object would implement IObserver, it would be able to 
connect to any subject. (If anyone is interested in this implementation, let me 
know and I'll post another article, but there are gazillions of examples on the net 
if you search a little). This works perfectly, but after working with interfaces a 
little, I decided not to use them. The main reason for this is that code navigation 
in the IDE is harder, Ctrl-Click brings you to the interface definition, not the 
implementation. No big deal. But I wanted something better. 

The second solution, the one I'm describing in this article, is a little different. 
The Observer is no longer an object, it's a method. Just like standard VCL events. 
This means that a single event handler could be used with a component and a Subject 
at the same time. 

Let say we have a TSubject with an event called OnChange. This event is of type 
TMultiNotifyEvent. Here is how a user would connect to this event. ChangeHandler is 
a procedure matching a TNotifyEvent. 

MySubject.OnChange.Attach(ChangeHandler);

From that point on, every time MySubject changes, it will call ChangeHandler, just 
like a normal OnChange event. The difference is that there might be many observers 
of that event. When the object no longer wish to receive updates from MySubject, it 
detaches: 

MySubject.OnChange.Detach(ChangeHandler);

In order for TMySubject to use a TMultiNotifyEvent, it must create it like this: 

1   type
2     TMySubject = class
3     private
4       FOnChange: TMultiNotifyEvent;
5     protected
6       procedure DoChange; virtual;
7     public
8       constructor Create;
9       destructor Destroy; override;
10      property OnChange: TMultiNotifyEvent read FOnChange;
11    end;
12  
13  implementation
14  
15  constructor TMySubject.Create;
16  begin
17    inherited;
18    FOnChange := TMultiNotifyEvent.Create;
19  end;
20  
21  destructor TMySubject.Destroy;
22  begin
23    FOnChange.Free;
24    inherited;
25  end;
26  
27  procedure TMySubject.DoChange;
28  begin
29    { Signal is the method that notify every observers }
30    OnChange.Signal(Self);
31  end;


In order to use a new type of event, one must declare a new class inheriting from 
TMultiEvent. Why am I doing this? Because TMultiEvent only stores and knows about 
TMethod records, it does not know what type of event is used, what are it's 
parameters, etc. By having the SignalObserver method Abstract, each concrete class 
can typecast it to the type of events it handles and pass all the required 
parameters. Also, creating a new class provices a certain level of type by making 
sure you register a compatible method with the subject. See TMultiNotifyEvent at 
the end of this article to see how to create customized events, it's pretty 
straight forward. 

That's it for the explication, if you have any questions just leave a comment and 
I'll try to update the article accordingly. 

32  unit uMultiEvent;
33  
34  interface
35  
36  uses
37    Classes, SysUtils;
38  
39  type
40    TMultiEvent = class
41    private
42      FObservers: TList;
43    protected
44      function FindObserver(Observer: TMethod): integer;
45      function GetObserver(Index: integer): TMethod;
46      procedure SignalObserver(Observer: TMethod); virtual;
47    public
48      constructor Create;
49      destructor Destroy; override;
50  
51      procedure Attach(Observer: TMethod);
52      procedure Detach(Observer: TMethod);
53  
54      procedure Signal;
55    end;
56  
57    TMultiNotifyEvent = class(TMultiEvent)
58    private
59      FSender: TObject;
60    protected
61      procedure SignalObserver(Observer: TMethod); override;
62    public
63      procedure Attach(Observer: TNotifyEvent);
64      procedure Detach(Observer: TNotifyEvent);
65  
66      procedure Signal(Sender: TObject);
67    end;
68  
69  implementation
70  
71  { TEvent }
72  
73  procedure TMultiEvent.Attach(Observer: TMethod);
74  var
75    Index: integer;
76  begin
77    Index := FindObserver(Observer);
78  
79    { This assertion is facultative, we could just ignore observers  }
80    { already attached, but it's a good way to detect problems early }
81    { and avoid unnecessary processing.                              }
82  
83    Assert(Index < 0, 'This observer was already attached to this event');
84  
85    { A method contains two pointers:                              }
86    { - The code pointer, that's where the procedure is in memory  }
87    { - The data pointer, this tells Delphi what instance of the   }
88    {   object calls the procedure                                 }
89    { We must store both pointers in order to use that callback.   }
90  
91    if Index < 0 then
92    begin
93      FObservers.Add(Observer.Code);
94      FObservers.Add(Observer.Data);
95    end;
96  end;
97  
98  constructor TMultiEvent.Create;
99  begin
100   inherited;
101   FObservers := TList.Create;
102 end;
103 
104 destructor TMultiEvent.Destroy;
105 begin
106   { This assertion is facultative, but I prefer when all my objects }
107   { are "clean" when they are destroyed.                            }
108   Assert(FObservers.Count = 0, 'Not all observers were detached');
109   FreeAndNil(FObservers);
110   inherited;
111 end;
112 
113 procedure TMultiEvent.Detach(Observer: TMethod);
114 var
115   Index: integer;
116 begin
117   Index := FindObserver(Observer) * 2;
118 
119   { Again, the assertion is facultative, nothing would be broken }
120   { if we just ignored it.                                       }
121   Assert(Index >= 0, 'The observer was not attached to this event');
122 
123   if Index >= 0 then
124   begin
125     FObservers.Delete(Index); // Delete code pointer
126     FObservers.Delete(Index); // Delete data pointer
127   end;
128 end;
129 
130 function TMultiEvent.FindObserver(Observer: TMethod): integer;
131 var
132   i: integer;
133 begin
134   { Search fails by default, if there is a match, result will be updated. }
135   Result := -1;
136   for i := (FObservers.Count div 2) - 1 downto 0 do
137   begin
138     { We have a match only if both the Code and Data pointers are the same. }
139     if (Observer.Code = FObservers[i * 2]) and (Observer.Data = FObservers[i * 2 + 
140 1])
141       then
142     begin
143       Result := i;
144       break;
145     end;
146   end;
147 end;
148 
149 function TMultiEvent.GetObserver(Index: integer): TMethod;
150 begin
151   { Fill the TMethod record with the code and data pointers. }
152   Result.Code := FObservers[Index * 2];
153   Result.Data := FObservers[Index * 2 + 1];
154 end;
155 
156 procedure TMultiEvent.SignalObserver(Observer: TMethod);
157 begin
158   { Descendants must take care to notify the Observer by themselves }
159   { because we cannot know the parameters required by the event.    }
160 
161   Assert(Assigned(@Observer));
162   { We could make this method Abstract and force descendants, but   }
163   { I prefer to do a run-time check to validate the passe methods   }
164 end;
165 
166 procedure TMultiEvent.Signal;
167 var
168   i: integer;
169 begin
170   { Call SignalObserver for each stored observers in reverse order. }
171 
172   { SignalObserver (which is declared in sub-classes) will typecast }
173   { the TMethod record into whatever procedure type it handles.     }
174   { See the TMultiNotifyEvent below for an example.                 }
175 
176   for i := (FObservers.Count div 2) - 1 downto 0 do
177   begin
178     SignalObserver(GetObserver(i));
179   end;
180 end;
181 
182 { TMultiNotifyEvent }
183 
184 procedure TMultiNotifyEvent.Attach(Observer: TNotifyEvent);
185 begin
186   inherited Attach(TMethod(Observer));
187 end;
188 
189 procedure TMultiNotifyEvent.Detach(Observer: TNotifyEvent);
190 begin
191   inherited Detach(TMethod(Observer));
192 end;
193 
194 procedure TMultiNotifyEvent.Signal(Sender: TObject);
195 begin
196   FSender := Sender;
197   inherited Signal;
198 end;
199 
200 procedure TMultiNotifyEvent.SignalObserver(Observer: TMethod);
201 begin
202   inherited;
203   TNotifyEvent(Observer)(FSender);
204 end;
205 
206 end.


			
Vote: How useful do you find this Article/Tip?
Bad Excellent
1 2 3 4 5 6 7 8 9 10

 

Advertisement
Share this page
Advertisement
Download from Google

Copyright © Mendozi Enterprises LLC