C#

Observer Design Pattern with an example in CSharp

Definition

Observer design pattern defines a one-to-many dependency between the objects. When the object state changes, the observers are notified/updated about the change.

Observer Pattern Terminology

The observer pattern will have a subject, concrete subject, observer, and concrete observers.

Subject: The subject is the interface that will be implemented by the concrete subject.

Concrete subject: The concrete subject implements the subject interface. It manages the state of the object. When a state change is detected, it sends updates about the change.

Observer: All concrete observers implements this interface

Concrete Observers: These are the candidates that receive updates from the subject when an object state is changed.

Diagram: Observer design pattern

Implementing observer pattern

Let’s say, for NBA, we have the NBA score broadcaster, who publishes scores to its observers.

We can check the scores in several different ways. We can see it on google, ESPN app, yahoo sports, CBS sports, etc. Let’s only consider publishing just the scores of two teams playing and the time on the clock.

So, our subject will be NBA and our observers will be the different news organizations registering as an observer to the subject.

Let’s first create interfaces for the Subject and Observer.

public interface ISubject 
{
  void RegisterObserver(IObservable o);
  void RemoveObserver(IObservable o);
  void NotifyObservers();
}

public interface IObservable 
{
  void Update(NBAScore score);
}

And here is the NBAScore model class

    public class NBAScore
    {
        public string GameClock { get; set; }
        public Team Team1 { get; set; }
        public Team Team2 { get; set; }
    }

    public class Team
    {
        public string Name { get; set; }
        public int Score { get; set; }
    }

The Subject

Let’s see how the subject can be implemented. I’m calling our NBA broadcaster to be just NBA.cs.

using System.Collections.Generic;

namespace DesignPatterns.Observer_Pattern
{
    /// <summary>
    /// The concrete Subject
    /// </summary>
    public class NBA : ISubject
    {
        private NBAScore _score { get; set; }
        private readonly List<IObservable> _observers = new List<IObservable>();

        public void RegisterObserver(IObservable o)
        {
            if (o != null)
            {
                _observers.Add(o);
            }
        }

        public void RemoveObserver(IObservable o)
        {
            _observers.Remove(o);
        }

        public void SetNewScore(NBAScore score)
        {
            _score = score;

            NotifyObservers();
        }

        public void NotifyObservers()
        {
            foreach (var observer in _observers)
            {
                observer.Update(_score);
            }
        }
    }
}

The NBA.cs implements the ISubject so that the observers can register or remove as an observer.

Within our concrete subject(NBA), we maintain a list of observers. When a new observer wants to be added/removed we will manage in the subject.

TheSetNewScoremethod is called when there’s a change in the score and it calls the NotifyObservers method which then notifies about the change to its observers.

Note: Technically we are not comparing the value of the score by comparing with the previous values to see if the change has really happened. Instead, we treat the method call to SetNewScore as a change and we will trigger updates to the observers.

That’s it about the subject. Let’s create a concrete implementation of the observer.

The Observer

Let’s say, we have the ESPN app that wants to be notified when the scores change. So, we will register ESPN as an observer to the NBA (the subject)

Here’s how the ESPN class looks like

using System;

namespace DesignPatterns.Observer_Pattern
{
    public class ESPN : IObservable
    {
        private NBAScore _score;
        private readonly ISubject _subject;

        public ESPN(ISubject subject)
        {
            _subject = subject ?? throw new ArgumentException("subject is required");
            _subject.RegisterObserver(this);
        }

        public void Update(NBAScore score)
        {
            _score = score;

            Display();
        }

        private void Display()
        {
            Console.WriteLine("--------------------------------------------");
            Console.WriteLine("LIVE: score update only on ESPN");
            Console.WriteLine($"GAME CLOCK: {_score.GameClock}");
            Console.WriteLine($"{_score.Team1.Name}: {_score.Team1.Score}");
            Console.WriteLine($"{_score.Team2.Name}: {_score.Team2.Score}");
            Console.WriteLine("--------------------------------------------");
        }
    }
}

Within the constructor, we registered our ESPN to the subject with the RegisterObserver method call. (We could also have a separate method to register by calling the method in the ESPN object)

We will implement the Update method in the IObservable interface to get the updates via the method.

Once we have the Update method implemented, we will call the Display method which takes care of displaying how we want the scores to be displayed.

We are done with our observer and subject setup. Let’s see this in action.

static void Main(string[] args) 
{
  var nba = new NBA();
  var nbaScore = new NBAScore {
    GameClock = "10:00",
    Team1 = new Team {
      Name = "GSW",
      Score = 25
    },
    Team2 = new Team {
      Name = "LAL",
      Score = 26
    }
  };

  var espn = new ESPN(nba);
  nba.SetNewScore(nbaScore);

  nbaScore.GameClock = "7:00";
  nbaScore.Team1.Score = 30;
  nbaScore.Team2.Score = 29;

  Thread.Sleep(1000);
  nba.SetNewScore(nbaScore);
}

First up, we created nba and nbaScore objects and pass the created nba object to espn constructor to register our observer to the nba (subject).

And then we will call SetNewScore and that should print out the scores of each team.

And we also set the SetNewScore method again to see if the new score gets updated and that should trigger the updates to the observers.

Here’s the output.

--------------------------------------------
LIVE: score update only on ESPN
GAME CLOCK: 10:00
GSW: 25
LAL: 26
--------------------------------------------
--------------------------------------------
LIVE: score update only on ESPN
GAME CLOCK: 7:00
GSW: 30
LAL: 29
--------------------------------------------

What problem did the observer design pattern solve?

If we have to create a new class what is the minimum change required? None. We don’t change the existing code.

To create a new observer, we have to just create a new class and implement the IObservable interface to receive the updates from the subject. So there are no changes to the subject or the other observers.

Let’s create a new observer called Yahoo.

public class Yahoo: IObservable 
{
  private NBAScore _score;
  private ISubject _subject;

  public Yahoo(ISubject subject) 
  {
    _subject = subject ?? throw new ArgumentException("subject is required");
    _subject.RegisterObserver(this);
  }
  
  public void Update(NBAScore score) 
  {
    _score = score;
    Display();
  }

  private void Display() 
  {
    Console.WriteLine("--------------------------------------------");
    Console.WriteLine("Yahoo score update !!!");
    Console.WriteLine($ "{_score.Team1.Name}: {_score.Team1.Score}");
    Console.WriteLine($ "{_score.Team2.Name}: {_score.Team2.Score}");
    Console.WriteLine($ "GAME CLOCK: {_score.GameClock}");
    Console.WriteLine("--------------------------------------------");
  }
}

In order for this to work, we’ve to plug this in our Main method by passing nba object to the constructor.

var yahoo = new Yahoo(nba);

That’s it! Our new Yahoo subscriber also should get updates when new scores are set.

The observer design pattern helps to achieve the Open-Closed Principle (OCP). Per the OCP principle, entities should be open for extension but closed for modification.

As you can see we didn’t change any of the existing code, we just created our new observer.

Decoupling observer registration and notification from the subject in the observer pattern (OPTIONAL)

You may have noticed the concrete subject (NBA.cs) violates the Single Responsibility Principle (SRP) as it does two things

  • Sets new scores
  • Maintains a list of observers, registers, removes and notifies observers

This decoupling is just an optional thing to do in the observer design pattern.

Let’s separate the observer operations from scores. First, we’ve to split the ISubject interface into two different interfaces.

  1. Registering/Removing observers (Let it be ISubject)
  2. Notifying observers with the score data (INotifier)

Here is our modified ISubject interface.

public interface ISubject 
{
  void RegisterObserver(IObservable o);
  void RemoveObserver(IObservable o);
}

And a new interface for notifying.

public interface INotifier 
{
  void Notify(NBAScore data);
}

And here is our modified version of the NBA class

public class NBA 
{
  private NBAScore Score { get; set; }
  private readonly INotifier _notifier;
  public NBA(INotifier notifier) 
  {
    _notifier = notifier ?? throw new ArgumentException(nameof(notifier));
  }

  public void SetNewScore(NBAScore score) 
  {
    Score = score;

    NotifyObservers();
  }

  public void NotifyObservers() 
  {
    _notifier.Notify(Score);
  }
}

Now, our NBA class now has no knowledge of any of the subscriber actions. It just calls the Notify method on the INotifier interface, which takes care of notifying the observers.

And here is our concrete class which implements both the ISubject and INotifier interfaces.

public class ObserverManager: ISubject, INotifier 
{
  private readonly List<IObservable> _observers = new List<IObservable>();

  public void RegisterObserver(IObservable o) 
  {
    if (o != null) 
    {
      _observers.Add(o);
    }
  }

  public void RemoveObserver(IObservable o) 
  {
    _observers.Remove(o);
  }

  public void Notify(NBAScore score) 
  {
    foreach (var observer in _observers) 
    {
      observer.Update(score);
    }
  }
}

And again we don’t need to change anything in our observer code. The observer still gets the updates as they have implemented the Update method.

Here is our main method.

static void Main(string[] args) 
{
  var manageObserver = new ObserverManager();
  var espn = new ESPN(manageObserver);
  var nba = new NBA(manageObserver);

  var nbaScore = new NBAScore 
  {
    GameClock = "10:00",
    Team1 = new Team 
    {
      Name = "GSW",
      Score = 25
    },
    Team2 = new Team 
    {
      Name = "LAL",
      Score = 26
    }
  };

  nba.SetNewScore(nbaScore);

  nbaScore.GameClock = "7:00";
  nbaScore.Team1.Score = 30;
  nbaScore.Team2.Score = 29;

  Thread.Sleep(1000);
  nba.SetNewScore(nbaScore);
}

Notice the nba and espn classes now take ObserverManager instance in their constructors. And we will just call the SetNewScore method when we have a new score as usual.

Let’s compare the flow of typical observer pattern without decoupling and with decoupling the observer management.

Diagram: After decoupling the observer management from the subject (NBA.cs)

One of the benefits of decoupling observer management is that if you want to change how we store/manage/trigger the observers it is easy to do and we do it without modifying the subject.

And again this decoupling is completely OPTIONAL.

References

Thanks for reading.

Disqus Comments Loading...
Share
Published by
Karthik Chintala

Recent Posts

2 Good Tools To Test gRPC Server Applications

In this post, we’ll see how to test gRPC Server applications using different clients. And… Read More

2 years ago

Exploring gRPC project in ASP.NET Core

In this post, we'll create a new gRPC project in ASP.NET Core and see what's… Read More

2 years ago

Run dotnet core projects without opening visual studio

In this blog post, we’ll see how to run dotnet core projects without opening visual… Read More

2 years ago

Programmatically evaluating policies in ASP.NET Core

Programmatically evaluating policies is useful when we want to provide access or hide some data… Read More

2 years ago

Multiple authorization handlers for the same requirement in ASP.NET Core

We saw how we could set up policy-based authorization in our previous article. In this… Read More

2 years ago

Policy-Based Authorization in ASP.NET Core

What is policy-based authorization and how to set up policy-based authorization with handlers and policies… Read More

2 years ago

This website uses cookies.