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.
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.
TheSetNewScore
method 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.
- Registering/Removing observers (Let it be
ISubject
) - 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.
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.
Karthik is a passionate Full Stack developer working primarily on .NET Core, microservices, distributed systems, VUE and JavaScript. He also loves NBA basketball so you might find some NBA examples in his posts and he owns this blog.
Pingback: Dew Drop – January 27, 2021 (#3368) – Morning Dew by Alvin Ashcraft
Pingback: The .NET Stacks #35: Nothing is certain but death and expiring certificates - Software Mile.com