Quick Start On Observer Design Pattern

Background

There are a lot of articles related to Design patterns with different programming languages, approaches, and representations. Here I would like to bring a simplified approach with realistic representation with an example. Here I am going to add an article on one of the Behavioural Design Patterns, which is the Observer Design Pattern. This pattern is used when there is a one-to-many relationship between objects, if one object is modified, its dependent objects are to be notified automatically. This Observer design pattern adheres to the Open-Closed principle.

Real-world Analogy

Let’s keep techy terminology aside and understand the common term.

If you look around the real world, there are a lot of examples that will get you to know about Observer Pattern,

  1. Your Television and a remote
  2. Your Car and a keyless remote
  3. Newspaper vendor and House/Shop
  4. eCommerce website like Amazon and Flipkart to notify on product availability or Price drop or an offer
  5. Stock exchange rate notification to Investors
  6. Your mobile phone software updating status with a percentage

And many more. If something is unique, please do post it in a comment box.

Example with Illustration

Let’s consider, a book store and student. Here a Student would like to buy a novel that has been published recently in the market. He visits the nearest book store to buy the novel, unfortunately, that novel had gone out of stock in the store.

 

After a few weeks, he visits the nearest book store again and it seems new stock has still not arrived. The poor student keeps visiting the store and still can't find the novel. Now, the book storekeeper realizes that it's inconvenient for someone to keep visiting and leaving empty handed. So the bookstore upgrades the system to keep the data of each person who visits to store. When any new books arrives in stock, immediately  message/email is sent to all users in the book store database.

If you look at the system now, it sends the message/email to all users in the book store database and those messages might be helpful for some users and some are not. Those messages will remain as spam messages as represented below,

Now, again the book store keeper upgrades the system to notify only interested users. Now, the book store keeper will register interested users to get a notification on new book arrivals as represented below,

Let’s get a bit technical now to understand the terminology of the observer pattern. Here book store acts as a Subject, Users act as observers. Here subject maintains a list of its dependents, called observers, and notifies all registered observers automatically of any state changes.

When can we use the Observer design pattern?

  1. A one-to-many dependency between objects, making the objects loosely coupled.
  2. When one object changes state, the number of dependent objects is updated automatically.

Pros

  1. The Subject and observers are loosely coupled. Subject and observers have no explicit knowledge of each other. Observers can be added and removed independently at run-time. It falls in the Open-Closed principle.
  2. Establishment of relations between objects at runtime.

Cons

  1. Observers are notified in random order (Asynchronous)
  2. The observer pattern can cause memory leaks, because the subject holds strong references to the observers, keeping them alive.

UML Representation

In the above UML class diagram, the Subject class does not update the state of dependent objects directly. Instead, Subject refers to the Observer interface (update()) for updating state, which makes the Subject independent of how the state of dependent objects is updated. The Observer A, Observer B, and Observer C classes implement the Observer interface by synchronizing their state with the subject's state.

Code example - Stock Exchange rates notifier

  • Define ‘Subject’, Here Subject (StockExchange) refers to the Observer interface (IInvestor) and it makes Subject Independent.
  • Investor can be added or removed by Register() and UnRegister() function.

All registered Investors will be notified by Notify() function.

namespace DP.ObserverPattern  
{  
    using System;  
    using System.Collections.Generic;  
    using System.Threading;  
  
    /// <summary>  
    /// 'Subject'  
    /// </summary>  
    public abstract class StockExchange  
    {  
        private string _companyName;  
        private double _price;  
        private List<IInvestor> _investors = new List<IInvestor>();  
  
        public StockExchange(string companyName, double price)  
        {  
            _companyName = companyName;  
            _price = price;  
        }  
  
        public void Register(IInvestor investor)  
        {  
            _investors.Add(investor);  
            Console.WriteLine($"Investor {investor.Name} Registered!");  
        }  
  
        public void UnRegister(IInvestor investor)  
        {  
            _investors.Remove(investor);  
            Console.WriteLine($"Investor {investor.Name} Un-registered!");  
        }  
  
        public void Notify()  
        {  
            Console.WriteLine($"DateTime: {DateTime.Now}");  
            foreach (var investor in _investors)  
            {  
                investor.Update(this);  
            }  
        }  
  
        public string CompanyName  
        {  
            get { return this._companyName; }  
        }  
  
        public double Price  
        {  
            get { return _price; }  
            set  
            {  
                if (_price != value)  
                {  
                    _price = value;  
                    Notify();  
                    Thread.Sleep(1000);  
                }  
            }  
        }  
    }  
}  

Define ‘Concrete Subject’ as SBI Stock Exchange,

namespace DP.ObserverPattern  
{  
    /// <summary>  
    /// 'Concrate Subject'  
    /// </summary>  
    public class SBI   
        : StockExchange  
    {  
        public SBI(double price)   
            : base("SBI", price)  
        {  
  
        }  
    }  
}  

Define ‘Observer’,

namespace DP.ObserverPattern  
{  
    /// <summary>  
    /// 'Observer' interface  
    /// </summary>  
    public interface IInvestor  
    {  
        string Name { get; }  
        void Update(StockExchange stockExchange);  
    }  
}  

Define ‘Concrete Observer’ as an Investor,

namespace DP.ObserverPattern  
{  
    using System;  
  
    /// <summary>  
    /// 'Concrate Observer'  
    /// </summary>  
    public class Investor  
        : IInvestor  
    {  
        private string _name;  
        private StockExchange _stockExchange;  
        public Investor(string name)  
        {  
            _name = name;  
        }  
  
        public string Name  
        {  
            get { return _name; }  
        }  
  
        public void Update(StockExchange stockExchange)  
        {  
            _stockExchange = stockExchange;  
            Console.WriteLine($"-> Notified { _name } of { _stockExchange.CompanyName }'s has changed to ${ _stockExchange.Price }");  
        }  
    }  
}  

Program Execution

  • Define the Subject as SBI with initial stock exchange rate = 0.0
  • Define the Observer as Investors like Investor-1, Investor-2, Investor-3, and Investor-4
  • Register/UnRegister the Investors with the SBI stock exchange.

Set the stock exchange rate and all the registered investors will get notified instantly.

namespace DP.ObserverPattern  
{  
    using System;  
  
    public class Program  
    {  
        static void Main(string[] args)  
        {  
            //Subject  
            SBI sbi = new SBI(0.0);  
  
            //Observer  
            var i1 = new Investor("Investor-1");  
            var i2 = new Investor("Investor-2");  
            var i3 = new Investor("Investor-3");  
            var i4 = new Investor("Investor-4");  
  
            sbi.Register(i1);  
            sbi.Register(i2);  
            sbi.Register(i3);  
  
            sbi.Price = 159.10;  
            sbi.Price = 157.50;  
            sbi.Price = 158.49;  
  
            sbi.UnRegister(i3);  
  
            sbi.Price = 159.33;  
            sbi.Price = 159.40;  
  
            sbi.Register(i4);  
  
            sbi.Price = 156.01;  
  
            Console.ReadLine();  
        }  
    }  
}  

Output

On the output screen, you can see the notification of stock exchange rates only for registered investors with SBI. Here Observers can be added and removed independently at run-time and make the system loosely coupled.

I hope this article helps you to understand this simple approach. Thanks for reading the article.