Introduction
We've all read about the Design patterns through various years of our Software Development Career. Of all the patterns, the coolest and the most practical I have come across is The Observer Pattern a.k.a. Publisher-Subscriber Pattern. You'll find various articles on how to implement the observer pattern in .NET Framework using the assemblies provided in the framework. BUT, .NET Framework has evolved over the years and along with that, it has also been providing new libraries for creating the Observer Pattern.
For instance, before .NET 4.0, we had to write custom code to implement Observer Pattern or use delegates and events. With the release of framework 4.0 came the very cool IObservable
and IObserver
interfaces for implementing Observer Pattern. We'll go through all these techniques with code examples in this article.
Background
To give you all a brief idea of the Observer Pattern, it defines a one-to-many dependency between objects (Publisher and multiple Subscribers) so that when one object (Publisher
) changes state, all the dependents (Subscriber
) are notified and updated automatically.
The above definition is highly influenced from the Book "Head First Design Patterns". I would strongly recommend anyone beginning to learn Design Patterns to read this book.
Sample Scenario
The code below is based on a scenario wherein we have a weather station which records weather data (temperature, humidity and pressure).
This data has to be consumed by multiple displays and displayed accordingly. Everytime there is new data available to the weather station, it should "push" the data to the displays which should all be updated accordingly. Suppose we have 3 displays (Current Conditions, Statistics Display and Forecast Display), all of these have to be updated whenever new data is available at the Weather Station.
The scenario is similar to the one presented in the Book "Head First Design Patterns", BUT here, we'll be discussing the .NET implementation of the same.
Implementation
So the Weather Data object is actually an object having 3 properties - temperature, humidity and pressure.
The WeatherData
class looks like the one below:
public class WeatherData
{
public float Temperature { get; set; }
public float Humidity { get; set; }
public float Pressure { get; set; }
public WeatherData(float temp, float humid, float pres)
{
Temperature = temp;
Humidity = humid;
Pressure = pres;
}
}
}
Now, this piece of data needs to be pushed to the weather displays (subscribers) whenever new data is available at the weather station.
Our code samples here would focus on implementing the observer pattern pre-.NET 4.0 and post .NET 4.0 .
The code in each section would be categorized into Publisher Code (Classes and Interfaces) and Subscriber Code (Classes and Interfaces). So let's get on with it.
Observer Pattern (before .NET 4.0)
<Technique #1>
Using Pure Object Oriented (OO) Programming Concepts
As per OO Best practices, we should always try to program to interfaces and not implementation. What follows is based on that.
Publisher
The Publisher
here is actually a Weather Data Provider which provides the weather data. This WeatherDataProvider
Class implements the IPublisher
interface as shown below:
public interface IPublisher
{
void RegisterSubscriber(ISubscriber subscriber);
void RemoveSubscriber(ISubscriber subscriber);
void NotifySubscribers();
}
The three methods above do the following:
RegisterSubscriber
- Registers a new subscriber with the Publisher
. The publisher has to add the subscribers to the list of subscribers it has to notify whenever WeatherData
has changed. RemoveSubscriber
- Removes a registered subscriber
from the publisher
's notification list NotifySubscibers
- This method actually invokes a method on the subscriber
object to notify that some new WeatherData
is available
The concrete implementation of a WeatherDataProvider
would be as below:
public class WeatherDataProvider : IPublisher
{
List<ISubscriber> ListOfSubscribers;
WeatherData data;
public WeatherDataProvider()
{
ListOfSubscribers = new List<ISubscriber>();
}
public void RegisterSubscriber(ISubscriber subscriber)
{
ListOfSubscribers.Add(subscriber);
}
public void RemoveSubscriber(ISubscriber subscriber)
{
ListOfSubscribers.Remove(subscriber);
}
public void NotifySubscribers()
{
foreach (var sub in ListOfSubscribers)
{
sub.Update(data);
}
}
private void MeasurementsChanged()
{
NotifySubscribers();
}
public void SetMeasurements(float temp, float humid, float pres)
{
data = new WeatherData(temp, humid, pres);
MeasurementsChanged();
}
}
Subscriber
The subscribers here are actually the weather displays, which consume the data. Each subscriber should implement the ISubscriber
interface
.
public interface ISubscriber
{
void Update(WeatherData data);
}
The Subscriber
interface
just has once method; It displays the current WeatherData
received from the WeatherDataProvider
.
An implementation of the CurrentConditionsDisplay
is as under:
public class CurrentConditionsDisplay : ISubscriber
{
WeatherData data;
IPublisher weatherData;
public CurrentConditionsDisplay(IPublisher weatherDataProvider)
{
weatherData = weatherDataProvider;
weatherData.RegisterSubscriber(this);
}
public void Display()
{
Console.WriteLine("Current Conditions :
Temp = {0}Deg | Humidity = {1}% |
Pressure = {2}bar", data.Temperature, data.Humidity, data.Pressure);
}
public void Update(WeatherData data)
{
this.data = data;
Display();
}
}
In the above code, we have done a Dependency Injection or IoC wherein we have injected the IPublisher interface
via the Constructor. Constructor Injection is a very common practice in OO programming.
So, what happens in the above code is that when the display in instantiated, it makes a call to the RegisterSubscriber
method of the WeatherDataProvider
and registers itself as an interested subscriber.
If a display wants to unregister itself, it has to call the RemoveSubscriber
method of the WeatherDataProvider
. There are multiple ways of accomplishing it - you can make a call to RemoveSubscriber
in the Destructor
(as above) or can implement IDisposable
and make a call to RemoveSubscriber
in Dispose
method, or just simply make a class method and make a call there.
Like CurrentConditionsDisplay
, we may have a ForecastDisplay
as well (as shown below):
public class ForecastDisplay : ISubscriber, IDisposable
{
WeatherData data;
IPublisher weatherData;
public ForecastDisplay(IPublisher weatherDataProvider)
{
weatherData = weatherDataProvider;
weatherData.RegisterSubscriber(this);
}
public void Display()
{
Console.WriteLine("Forecast Conditions : Temp = {0}Deg |
Humidity = {1}% | Pressure = {2}bar", data.Temperature + 6,
data.Humidity + 20, data.Pressure - 3);
}
public void Update(WeatherData data)
{
this.data = data;
Display();
}
public void Dispose()
{
weatherData.RemoveSubscriber(this);
}
}
Similarly, you could define any number of displays here, and they could subscribe to the WeatherStation
for updates.
For demonstrating the above piece of code, we'll make a sample Console Application as below:
class Program
{
static void Main(string[] args)
{
WeatherDataProvider weatherData = new WeatherDataProvider();
CurrentConditionsDisplay currentDisp = new CurrentConditionsDisplay(weatherData);
ForecastDisplay forecastDisp = new ForecastDisplay(weatherData);
weatherData.SetMeasurements(40, 78, 3);
Console.WriteLine();
weatherData.SetMeasurements(45, 79, 4);
Console.WriteLine();
weatherData.SetMeasurements(46, 73, 6);
forecastDisp.Dispose();
Console.WriteLine();
Console.WriteLine("Forecast Display removed");
Console.WriteLine();
weatherData.SetMeasurements(36, 53, 8);
Console.Read();
}
}
The output should be as under:
<Technique #2>
Using Events and Delegates
Now, onto the second way of implementing the observer pattern in .NET. The first method, of course, didn't make use of any of the .NET libraries for implementing the pattern.
This technique is very common in .NET whenever it comes to implement the Observer pattern. It makes use of Events and delegates in .NET framework to accomplish the same.
I would not delve much into the details of the below source code as most of it would be self explanatory, but if you do need more information on the below implementation, you could refer to this MSDN link.
To start with, this technique makes use of the generic EventHandler<T>
delegate to achieve the pattern. The basic theory is pretty straightforward:
- The publisher has a
public Event
and EventHandler
which it raises each time the WeatherData
changes. - The subscribers attach to the
EventHandler
to get notified whenever the event is raised. - The
EventArgs e
, received at the subscriber contain data about the current weather conditions.
To begin with, first, we have to create a WeatherEventArgs
class which inherits from EventArgs
:
public class WeatherDataEventArgs : EventArgs
{
public WeatherData data { get; private set; }
public WeatherDataEventArgs(WeatherData data)
{
this.data = data;
}
}
This class contains the Weather Data which will be passed as Event parameters in the Event handler.
Publisher
The Publisher
here is a class called WeatherDataProvider
which raises events whenever weather data changes and notifies the subscribed displays:
public class WeatherDataProvider : IDisposable
{
public event EventHandler<WeatherDataEventArgs> RaiseWeatherDataChangedEvent;
protected virtual void OnRaiseWeatherDataChangedEvent(WeatherDataEventArgs e)
{
EventHandler<WeatherDataEventArgs> handler = RaiseWeatherDataChangedEvent;
if (handler != null)
{
handler(this, e);
}
}
public void NotifyDisplays(float temp, float humid, float pres)
{
OnRaiseWeatherDataChangedEvent
(new WeatherDataEventArgs(new WeatherData(temp, humid, pres)));
}
public void Dispose()
{
if (RaiseWeatherDataChangedEvent != null)
{
foreach (EventHandler<WeatherDataEventArgs>
item in RaiseWeatherDataChangedEvent.GetInvocationList())
{
RaiseWeatherDataChangedEvent -= item;
}
}
}
}
The above code declares an EventHandler
delegate of generic type which takes in WeatherDataEventArgs
as arguments to be supplied to event handlers.
Each time the NotifyDisplays
method is called, the OnRaiseWeatherDataChangedEvent
is raised which in turn calls the event handlers in the respective Displays
(subscribers) in the following code.
Subscriber
The implementation of the CurrentConditionsDisplay
is as given below. We can have similarly any number of displays attaching to the Event
via Event
handlers.
public class CurrentConditionsDisplay
{
WeatherData data;
WeatherDataProvider WDprovider;
public CurrentConditionsDisplay(WeatherDataProvider provider)
{
WDprovider = provider;
WDprovider.RaiseWeatherDataChangedEvent += provider_RaiseWeatherDataChangedEvent;
}
void provider_RaiseWeatherDataChangedEvent(object sender, WeatherDataEventArgs e)
{
data = e.data;
UpdateDisplay();
}
public void UpdateDisplay()
{
Console.WriteLine("Current Conditions : Temp = {0}Deg |
Humidity = {1}% | Pressure = {2}bar", data.Temperature, data.Humidity, data.Pressure);
}
public void Unsubscribe()
{
WDprovider.RaiseWeatherDataChangedEvent -= provider_RaiseWeatherDataChangedEvent;
}
}
Like the one above, we could also have a ForecastDisplay
(available in source code).
A demo of the usage of the above can be done using a Console Application with code as under:
class Program
{
static void Main(string[] args)
{
WeatherDataProvider provider = new WeatherDataProvider();
CurrentConditionsDisplay current = new CurrentConditionsDisplay(provider);
ForecastDisplay forecast = new ForecastDisplay(provider);
provider.NotifyDisplays(40, 78, 3);
Console.WriteLine();
provider.NotifyDisplays(42, 68, 5);
Console.WriteLine();
provider.NotifyDisplays(45, 68, 8);
Console.WriteLine();
forecast.Unsubscribe();
Console.WriteLine("Forecast Display removed");
Console.WriteLine();
provider.NotifyDisplays(30, 58, 1);
provider.Dispose();
Console.Read();
}
}
Observer Pattern (.NET 4.0 and above)
When .NET framework was released, it got with it a myriad of requested language features for C# like dynamic type, optional parameters, IObservable
, etc. Some of these were features which were available in other languages/frameworks, BUT not in C#.
One of the most notable of them all was IObservable<T>
and IObserver<T>
. These have been available in Java before it came to C# and it is indeed one of my most favourites features. These Collections bring in some unique and nifty functionality from us developers to exploit. The most important being able to implement a robust Publisher-Subscriber model, a.k.a., Observer Pattern.
The below implementation is in line with the sample implementation in the MSDN link here.
<Technique #3>
Using IObservable<T> and IObserver<T>
These libraries make it the easiest to implement an Observer Pattern. In fact, it's so easy that you might not even realise that you are using a Publisher-Subsriber model in your program while using these libraries.
In the generic Type IObservable<T>
and IObserver<T>
, the T
in our case would be WeatherData
.
Publisher
The publisher (WeatherDataProvider
) has to just implement the IObservable<T>
interface (below).
public class WeatherDataProvider : IObservable<WeatherData>
{
List<IObserver<WeatherData>> observers;
public WeatherDataProvider()
{
observers = new List<IObserver<WeatherData>>();
}
public IDisposable Subscribe(IObserver<WeatherData> observer)
{
if (!observers.Contains(observer))
{
observers.Add(observer);
}
return new UnSubscriber(observers, observer);
}
private class UnSubscriber : IDisposable
{
private List<IObserver<WeatherData>> lstObservers;
private IObserver<WeatherData> observer;
public UnSubscriber(List<IObserver<WeatherData>> ObserversCollection,
IObserver<WeatherData> observer)
{
this.lstObservers = ObserversCollection;
this.observer = observer;
}
public void Dispose()
{
if (this.observer != null)
{
lstObservers.Remove(this.observer);
}
}
}
private void MeasurementsChanged(float temp, float humid, float pres)
{
foreach (var obs in observers)
{
obs.OnNext(new WeatherData(temp, humid, pres));
}
}
public void SetMeasurements(float temp, float humid, float pres)
{
MeasurementsChanged(temp, humid, pres);
}
}
Observers register to receive notifications by calling its IObservable<T>.Subscribe
method, which assigns a reference to the observer
object to a private
generic List<T>
object. The method returns an Unsubscriber
object, which is an IDisposable
implementation that enables observers to stop receiving notifications. The Unsubscriber
class is a simple nested class that implements IDisposable
and also keeps a list of Subscribed users and is used by the observers (Displays
in our case), to unsubscribe. We'll know more about that in the next section.
Also, there is a function OnNext
which we have invoked on the subscriber/observer. This function is actually an inbuilt function of IObserver
which indicates that something has changed in the collection. This is actually the function which notifies the subscribers of changes.
Apart from OnNext
, there is OnError
and OnCompleted
functions as well. We'll discuss all of these in the next section.
Subscriber
The subscriber (Displays
) have to just implement the IObserver<T>
interface
. The implementation of the CurrentConditionsDisplay
is as under. We can have similarly any number of displays (see source code):
public class CurrentConditionsDisplay : IObserver<WeatherData>
{
WeatherData data;
private IDisposable unsubscriber;
public CurrentConditionsDisplay()
{
}
public CurrentConditionsDisplay(IObservable<WeatherData> provider)
{
unsubscriber = provider.Subscribe(this);
}
public void Display()
{
Console.WriteLine("Current Conditions : Temp = {0}Deg |
Humidity = {1}% | Pressure = {2}bar", data.Temperature, data.Humidity, data.Pressure);
}
public void Subscribe(IObservable<WeatherData> provider)
{
if (unsubscriber == null)
{
unsubscriber = provider.Subscribe(this);
}
}
public void Unsubscribe()
{
unsubscriber.Dispose();
}
public void OnCompleted()
{
Console.WriteLine("Additional temperature data will not be transmitted.");
}
public void OnError(Exception error)
{
Console.WriteLine("Some error has occurred..");
}
public void OnNext(WeatherData value)
{
this.data = value;
Display();
}
}
Things of interest in the above code are as follows:
- When you make a call to
subscriber
method, it returns an object which implements IDisposable
(in this case Unsubscriber
). So when we call Dispose
on that object, it automatically calls Unsubscribe
. - There are 3 methods which can be invoked by the Publisher/provider on the subscriber/observers :
IObserver<T>.OnNext
method to pass the observer a T
object that has current data, changed data, or fresh data. IObserver<T>.OnError
method to notify the observer that some error condition has occurred (Note that this does NOT get automatically called whenever an exception occurs in the provider. Its actually the responsibility of the programmer to catch the exception in the provider and then invoke this function). IObserver<T>.OnCompleted
method to notify the observer that it has finished sending notifications
A demo program to watch this code in action can be made using a Console application with code as below:
class Program
{
static void Main(string[] args)
{
WeatherDataProvider weatherDataO = new WeatherDataProvider();
CurrentConditionsDisplay currentDisp = new CurrentConditionsDisplay(weatherDataO);
ForecastDisplay forecastDisp = new ForecastDisplay(weatherDataO);
weatherDataO.SetMeasurements(40, 78, 3);
Console.WriteLine();
weatherDataO.SetMeasurements(45, 79, 4);
Console.WriteLine();
weatherDataO.SetMeasurements(46, 73, 6);
forecastDisp.Unsubscribe();
Console.WriteLine();
Console.WriteLine("Forecast Display removed");
Console.WriteLine();
weatherDataO.SetMeasurements(36, 53, 8);
Console.Read();
}
}
Points of Interest
When I started studying up Design Patterns and especially the Observer Pattern, I discovered that there can be numerous ways of implementing the same in .NET Framework.
After some research, I have made peace with my curiosity and settled on the above 3 implementation techniques. Of course, you could have another cool implementation of the Observer pattern and I'll be happy to know about that. Sound off in the comments below.
History