Observer Pattern in .NET

0

Category :

a.k.a. Publisher-Subscriber Pattern.

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 IObservable and IObserver interfaces for implementing Observer Pattern.

Background

The Observer Pattern 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.

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 acordingly. 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.

Technique #1 - Using Pure Object Oriented (OO) programming concepts.

Publisher
Weather Data Provider which provides the weather data, WeatherDataProvider Class implements the IPublisher interface as shown below:
public interface IPublisher
{
	// Registers a new subscriber on the list of subscribers it has to 
    // notify whenever WeatherData has changed
    void RegisterSubscriber(ISubscriber subscriber);

    // Removes a registered subscriber from the publisher's notification list
    void RemoveSubscriber(ISubscriber subscriber);

    // This method actually invokes a method on the subscriber object to
    // notify that some new WeatherData is available
    void NotifySubscribers();
}
The concrete implementaion of a WeatherDataProvider would be as below:
public class WeatherDataProvider : IPublisher
{
    List ListOfSubscribers;
    WeatherData data;
    public WeatherDataProvider()
    {
        ListOfSubscribers = new List();
    }
    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 subcsriber should implement the ISubscriber interface.
public interface ISubscriber
{
    void Update(WeatherData data);
}
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();
    }
}
Above we have injected the IPublisher interface via the Constructor.
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.

Technique #3 - Using IObservable and IObserver.

In the generic Type IObservable and IObserver, the T in our case would be WeatherData.

Publisher

The publisher (WeatherDataProvider) has to just implement the IObservable interface (below).
public class WeatherDataProvider : IObservable
    {
        List> observers;

        public WeatherDataProvider()
        {
            observers = new List>();
        }

        public IDisposable Subscribe(IObserver observer)
        {
            if (!observers.Contains(observer))
            {
                observers.Add(observer);
            }
            return new UnSubscriber(observers, observer);
        }

        private class UnSubscriber : IDisposable
        {
            private List> lstObservers;
            private IObserver observer;

            public UnSubscriber(List> ObserversCollection, IObserver 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.Subscribe method, which assigns a reference to the observer object to a private generic List object. The method returns an Unsubscriber object, which is an IDisposable implementation that enables observers to stop receiving notifications. The Unsubscriber class is simple a 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.

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.

Subscriber

The subscriber (Displays) have to just implement the IObserver interface. The implementation of the CurrentConditionsDisplay is as under. We can have similarly any number of displays.
public class CurrentConditionsDisplay : IObserver
{
    WeatherData data;
    private IDisposable unsubscriber;

    public CurrentConditionsDisplay()
    {

    }
    public CurrentConditionsDisplay(IObservable 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 provider)
    {
        if (unsubscriber == null)
        {
            unsubscriber = provider.Subscribe(this);
        }
    }

    public void Unsubscribe()
    {
        unsubscriber.Dispose();
    }

    // Pass the observer a T object that has current data, changed data, or fresh data.
    public void OnNext(WeatherData value)
    {
        this.data = value;
        Display();
    }

    // Notify the observer that some error condition has occurred
    public void OnError(Exception error)
    {
        Console.WriteLine("Some error has occurred..");
    }

    // Notify the observer that it has finished sending notifications
    public void OnCompleted()
    {
        Console.WriteLine("Additional temperature data will not be transmitted.");
    }
}

my thanks to:
http://www.codeproject.com/Articles/796075/Implement-Observer-Pattern-in-NET-techniques

0 comments:

Post a Comment