Introduction

The Observer Pattern (also known as Publish-Subscribe Pattern) is a behavioral design pattern which defines a one-to-many dependency between objects so that when one object changes its state, all its dependents are notified and updated automatically. Observer Pattern allows for consistency between related objects without tightly coupling classes.

Key Participants

The classes and objects participating in this pattern are:

  1. Subject Interface

Knows its observers – provides interface for attaching/detaching subjects.

  1. Observer Interface

Defines an interface for notifying the subjects of changes to the object (e.g. Data)

  1. Concrete Subject

Sends notification to observers when state changes.

  1. Concrete Observer

Implements Observer interface.

Usage of Observer Design Pattern

A very common requirement in any complex system is the ability for an object to know whenever an event happens or whenever another object changes its state (which can also be viewed as an event).

Examples:

  1. Company updates all its shareholders for any decision they make. Here Company is Subject and Shareholders are Observers, and if there are any changes in company policy, then the Shareholders or Obeservers will be notified by the Company .
  2. A typical example for the Observer pattern is the conventional mail delivery system. Whenever you have mail, the postman will come and knock at your door with the mail. Just imagine if you had to go to the post office every day and check whether there is any mail for you. It would have been a really inefficient system as each individual would need to go to the post office periodically. This is eliminated in real life by introducing a mail delivery mechanism.

There are two primary approaches to learn about a change in the state of an object:

  • Polling – Any object that is interested in state changes will periodically inquire about thestate. This approach is challenging when working with events.
  • Notification (Observer Design Pattern) – Objects register to receive an update or notification whenever state changes or an event occurs. A publisher ensures that everyone is notified.

Class Diagram

The class diagram for the implementation of the observer design pattern is given below:

DotNetProg-1


using System;
using System.Collections.Generic;

namespace DesignPattern
{
    class Program
    {
        /// <summary>
        /// Entry point into console application.
        /// </summary>
        /// <param name="args"></param>
        static void Main(string[] args)
        {
            // Configure Observer pattern
            WeatherData weatherInfo = new WeatherData();
            CurrentConditionsDisplay display1 = new CurrentConditionsDisplay(weatherInfo, "Display 1");
            CurrentConditionsDisplay display2 = new CurrentConditionsDisplay(weatherInfo, "Display 2");
            // Change subject and notify observers
            weatherInfo.SetMeasurements(96, 63, 1007);
            // Wait for user
            Console.ReadKey();

        }
    }

    #region Interfaces
    /// <summary>
    /// Subject Interface. Each Subject concrete class in your application must implement this.
    /// </summary>
    public interface IWeatherPublisher
    {
        void AddSubscriber(IWeatherObserver subscriber);
        void RemoveSubscriber(IWeatherObserver subscriber);
    }

    /// <summary>
    /// Observer Interface. Each Observer concrete class in your application must implement this.
    /// </summary>
    public interface IWeatherObserver
    {
        void Update(IWeatherPublisher weatherData);
    }

    /// <summary>
    /// Interface for Observer to display the updated state of Subject.
    /// </summary>
    public interface IDisplayElement
    {
        void Display();
    }
    #endregion

    #region Subjects
    /// <summary>
    /// The 'Subject' class
    /// </summary>
    public class WeatherData : IWeatherPublisher
    {

        // List to hold the observers
        private readonly List<IWeatherObserver> observers = new List<IWeatherObserver>();
        private float temperature;
        private float humidity;
        private float pressure;

        /// <summary>
        /// Get Temperature
        /// </summary>
        public float Temperature
        {
            get { return temperature; }
        }

        /// <summary>
        /// Get Humidity
        /// </summary>
        public float Humidity
        {
            get { return humidity; }
        }

        /// <summary>
        /// Get Pressure
        /// </summary>
        public float Pressure
        {
            get { return pressure; }
        }

        /// <summary>
        /// Add Subscriber
        /// </summary>
        /// <param name="subscriber"></param>
        public void AddSubscriber(IWeatherObserver subscriber)
        {
            observers.Add(subscriber);
        }

        /// <summary>
        /// Remove Subscriber
        /// </summary>
        /// <param name="subscriber"></param>
        public void RemoveSubscriber(IWeatherObserver subscriber)
        {
            if (!observers.Contains(subscriber))
                throw new System.ArgumentException("Subscriber does not exist");
            observers.Remove(subscriber);
        }

        /// <summary>
        /// Set weather data
        /// </summary>
        /// <param name="temperature"></param>
        /// <param name="humidity"></param>
        /// <param name="pressure"></param>
        public void SetMeasurements(float temperature, float humidity, float pressure)
        {
            this.temperature = temperature;
            this.humidity = humidity;
            this.pressure = pressure;
            NotifyObservers();
        }

        /// <summary>
        /// Notify all observers
        /// </summary>
        private void NotifyObservers()
        {
            observers.ForEach(o => o.Update(this));
        }

    }
    #endregion

    #region Observers
    /// <summary>
    /// The 'Observer' class
    /// </summary>
    public class CurrentConditionsDisplay : IWeatherObserver, IDisplayElement
    {
        private float temperature;
        private float humidity;
        private string observerName;

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="weatherData"></param>
        /// <param name="name"></param>
        public CurrentConditionsDisplay(IWeatherPublisher weatherData, string name)
        {
            weatherData.AddSubscriber(this);
            this.observerName = name;
        }

        /// <summary>
        /// Update weather info.
        /// </summary>
        /// <param name="weatherData"></param>
        public void Update(IWeatherPublisher weatherData)
        {
            WeatherData data = (weatherData as WeatherData);
            this.temperature = data.Temperature;
            this.humidity = data.Humidity;
            Display();
        }

        /// <summary>
        /// Display weather info.
        /// </summary>
        public void Display()
        {
            Console.WriteLine(string.Format("{0} : Current conditions : {1}F degrees and {2}% humidity", observerName, temperature, humidity));
        }
    }
    #endregion

}

Observer Design Pattern – Output

DotNetProg-2

 

Download Sample Files

If you wish to play around a bit with my sample code, I have created a zip file that contains all the necessary files for you to do so.