Introduction
The Observer pattern should be used whenever one or more objects (observers) are interested in knowing the states of subject. The subject maintains a list of its observers. Each observer must register as an observer with the subject.
In this article, I have described how to implement the Observer pattern using C# cool features such as event
s and delegate
s.
Observer Pattern
Design Patterns are meant to provide a common vocabulary for communicating design principles. The Observer Pattern is classified under Object Behavioral Patterns in the book, Design Patterns: Elements of Reusable Object-Oriented Software by Erich Gamma et al. (Addison-Wesley, 1995). According to this book, definition of Observer pattern is:
"Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically."
Scenario
Say, code up a FileArchive
class which will notify interested parties whenever files are added or deleted. That means any class can publish a set of events, to which other objects can subscribe at run time.
In this article, consider a banking application where, I want various objects in the system to be able to obtain information on currency rate changes.
Solution
Anyone who has done GUI programming will be familiar with the idea of event
s, where an object tells interested parties that something significant has occurred. An example might include a button
object telling its parent form that it has been pressed. Sometimes we need this type of event
outside of GUI programming.
Event
s in C# are based on delegate
s, with the originator defining one or more callback functions. A callback function is a function which one piece of code defines and another implements; in other words, one piece of code says, "If you implement a function which looks like this, I can call it". A class that wants to use event
s defines callback functions as delegate
s, and the listening object then implements them. In this article, I have tried to discuss how to implement Observer pattern with the help of this event delegate
.
Consider a class named
CurrencyRateWatcher
which gets currency rate from various sources (through
Webservice
). If it's found that currency rate is changed, then this object can notify other objects in the banking system when a currency rate changes. In this case, I'll use a class called
CurrencyRateChangeObserver
, so I can simply show how events are set up. I'll use the classes like this:
public static Main(String[] args)
{
CurrencyRateWatcher
oRateWatcher = new CurrencyRateWatcher ();
CurrencyRateChangeObserver
obs = new CurrencyRateChangeObserver(oRateWatcher);
oRateWatcher.Notify("Dollar", 78,DateTime.Now);
}
The CurrencyRateWatcher
is created and started to monitor the currency rates. Then create a CurrencyRateChangeObserver
, passing it a reference to the watcher object. The CurrencyRateChangeObserver
tells the CurrencyRateWatcher
that it is interested in being notified, so its callback method will get called and adjust with the new value of currency whenever CurrencyRateWatcher
detects that the currency rate has changed.
The delegate
method that is used for the notification takes two arguments. The first is a reference to the object that has originated the notification, and the second contains any data which needs to be passed as part of the notification process. In our example, we'll want to pass over the name of the currency (such as Dollar, Pound, and Euro), new value and the change time. This information is passed in the second argument as a reference to a class derived from the system class EventArgs
:
Class CurrencyRateChangeInfo : EventArgs
{
public string CurrencyName;
public double newRate;
public double changeTime;
public CurrencyRateChangeInfo(string name, double rate, double chTime)
{
CurrencyName = name;
newRate = rate;
changeTime = chTime;
}
}
A CurrencyRateChangeInfo
is an object that simply holds the name of a currency, the new value for the currency and the time of change.
Now I am going to implement the CurrencyRateWatcher
class itself. The first task is to define the delegate
that will be used for callbacks. As I mentioned above, event delegates take two arguments, one representing the sender object, and the second the data passed with the event
:
Class CurrencyRateWatcher
{
public delegate void CurrencyRateChange (object sender,CurrencyRateChangeInfo info);
}
Now add a reference to an event
object:
Class CurrencyRateWatcher
{
public delegate void CurrencyRateChange(object sender,CurrencyRateChangeInfo info);
public event CurrencyRateChange OnRateChange;
}
The second line of the above code defines an event
object of type CurrencyRateChange
, called OnRateChange
. I don't implement this object-but only declare a public
reference to it. Clients will create the delegate
object and attach it to my reference, so that when I use the event
object, I am calling back to them.
Notification
The final part of CurrencyRateWatcher
is the notification method, which calls back to the client:
Class CurrencyRateWatcher
{
public delegate void CurrencyRateChange(object sender, CurrencyRateChangeInfo info);
public event CurrencyRateChange OnRateChange;
public void Notify(string name, double newValue)
{ if(OnRateChange != null)
{
CurrencyRateChangeInfo cRateChange = new CurrencyRateChangeInfo(name,newValue);
OnRateChange(this, cRateChange);
}
}
}
In the notification method, first I check the event
reference whether it is null
or not. If the event
reference is null
, then no one has registered with me. If it isn't null
, I create a CurrencyRateChangeInfo
object to hold the event
data, and then use the delegate
to call back to the client.
Client Who is Interested to be Notified
Now I am going to implement the client, in the form of the CurrencyRateChangeObserver
class:
class CurrencyRateChangeObserver
{
CurrencyRateWatcher wr;
public CurrencyRateChangeObserver(CurrencyRateWatcher wr)
{
this.wr = wr;
}
public void RateHasChanged (object sender, CurrencyRateChangeInfo oInfo)
{
Console.WriteLine("Rate '{0}' has
new value '{1}'
and the change Time is{2}", oInfo.
CurrencyName,
oInfo.newRate, oInfo.changeTime);
}
}
First, I implement the constructor to store away the reference we're passed. I also implement the delegate
in the form of the RateHasChanged
method, which takes two arguments that were defined in CurrencyRateWatcher
. This method just gets the name of the currency, new value and change time- from the CurrencyRateChangeInfo
object, and writes them to the command window.
Set Up Client
The final piece of the code is to link callback method to the event
reference in the watcher object:
class CurrencyRateChangeObserver
{
CurrencyRateWatcher wr;
public CurrencyRateChangeObserver(CurrencyRateWatcher wr)
{
this.wr = wr;
wr.OnRateChange +=
new CurrencyRateWatcher.CurrencyRateChange(RateHasChanged);
}
public void RateHasChanged (object sender, CurrencyRateChangeInfo oInfo)
{
Console.WriteLine("Rate '{0}' has
new value '{1}'
and the change Time is{2}", oInfo.
CurrencyName,
oInfo.newRate, oInfo.changeTime);
}
}
Using the standard delegate
syntax, I create a CurrencyRateWatcher
. CurrencyRateChange delegate
is passed a reference to callback method, and saved to the public OnRateChange
member of the watcher. Here '+=
' symbol is being used in this assignment - this is a very useful property of delegate
s and allows us to chain delegate
references together (+=
is actually the operator overloading for registering with event
). Thus, if more than one client registers itself with the same CurrencyRateWatcher
, their delegate
s will get concatenated, and when OnRateChange
is called in the watcher, all registered callback methods will get called one after another. And so we can see how it all fits together.
History
- 29th April, 2007: Initial post
This is S.M. Rabiul Islam from Bangladesh, I have been working as a software engineer here in Bangladesh at offshore software development house