What, Why & How To Use Observer Pattern
Observer pattern is one of the most commonly used design pattern. You probably have encountered the Observer Pattern in many places already. When parts of your application need to react to a value change in another part, observer pattern comes in very handy. Let’s see how observer pattern helps us write code that depends on other part of the code and yet remain loosely coupled.
The Problem
Suppose we are building a game where an objects holds the environment information, and the part responsible for displaying the scene or rendering character behaviour needs to know the current environment. Let’s define a class for holding the environment information.
class Environment
{
public string DayPart {get; set;} = "morning"; // "noon", "afternoon", "night". We should use an Enum here, but for simplicity, using string
public string RainCondition {get; set;} = "dry"; // "light", "heavy". Again, should have used an Enum here but using string for simplicity
public UpdateEnvironment()
{
outdoorScene.Update(DayPart, RainCondition);
character.ChangeBehaviour(DayPart, RainCondition);
}
}
The problem with this approach is, our Environment class and the classes that need the environment information are dependent on each other. The ‘UpdateEnvironment’ method needs to know that the ‘outdoorScene’ object has ‘Update’ method or the ‘character’ object has ‘ChangeBehaviour’ method. And if we introduce new parts in the code that also depend on the Environment information, the “UpdateEnvironment” method will need to change. If we add a new Environment property, say `WindCondition`, then we may also need to change the `Update` and `ChangeBehaviour` methods. As you can see, our code is very tightly coupled which breaks the following design principle:
Strive for loosely coupled designs between objects that interact.
Another problem with this implementation is, if we need to add or remove something in the runtime that depends on the Environment, we have no way to do it.
The Solution
We are going to use the observer pattern of course. Observer pattern works like a publisher-subscriber model. In the Observer pattern, we call the publisher a Subject or Provider and the subscribers are called Observers. One Subject can have many observers and when something changes in the Subject, it notifies all of its observers.
Let’s see the implementation. First we will define the interfaces for Subject and Observer.
interface ISubject
{
public void RegisterObserver(IObserver observer);
public void RemoveObserver(IObserver observer);
public void NotifyObservers();
}
interface IObserver
{
public void Update();
}
Our Environment is going to implement the ‘ISubject’ interface. Let’s see the implementation.
class Environment: ISubject
{
// properties needed for the concrete Environment class
public string DayPart { get; private set; } = "morning";
public string RainCondition { get; private set; } = "dry";
// we will use a List to hold the observers
private readonly List<IObserver> observers = [];
// implementation of the interface
public void RegisterObserver(IObserver obs)
{
observers.Add(obs);
}
public void RemoveObserver(IObserver obs)
{
observers.Remove(obs)
}
// the update happens here
public void NotifyObservers()
{
foreach (var obs in observers)
{
obs.Update();
}
}
// other methods per application need
public void SetEnvironment(string dayPart, string rainCondition)
{
DayPart = dayPart;
RainCondition = rainCondition;
// when any value changes, the Subject will call its NotifyObservers method
NotifyObservers();
}
}
As you can see, the `NotifyObservers` method is calling the `Update` method of all the registered observers. The Subject doesn’t need to know the exact implementation of the observer objects. It just knows that the observers should have an ‘Update’ method. All the classes that needs to react to Environment changes will implement the ‘IObserver’ interface hence will have the ‘Update’ method.
class OutdoorSceneRender: IObserver
{
private readonly Environment environment;
public OutddoorSceneRender(Environment env)
{
environment = env;
// registering the SceneRender object as an observer
environment.RegisterObserver(this);
}
public void Update()
{
// render logic goes here
// for example,
// if (environment.DayPart == "afternoon")
// {
// move sun to the left
// }
Console.WriteLine("Updating Outside Scene");
}
public void Remove()
{
// if we decide to remove the scene, it can unregister itself in runtime
environment.RemoveObserver(this);
// other scene removal logic ...
}
}
class Character: IObserver
{
private readonly Environment environment;
public Character(Environment env)
{
environment = env;
environment.RegisterObserver(this); // registering the SceneRender object as an observer
}
public void update()
{
// logic goes here
// for example,
// if (environment.RainCondition == "heavy")
// {
// reduce character movement speed
// }
Console.WriteLine("Updating character");
}
public void Remove()
{
environment.RemoveObserver(this);
// other removal logic ...
}
}
We can keep adding new classes that depends on the Environment class without needing to touch the Environment class. And if we add new properties to the Environment class, the classes that don’t need the new property will remain untouched. In other words, our code is independent of each other’s implementation hence very loosely coupled. The code is much more flexible and maintainable this way.
Now, let’s see the main program.
Environment environment = new ();
OutdoorSceneRender outdoorSceneRender = new(environment);
Character character = new (environment);
// Updating Environment in our game, related parts will get updated automatically
environment.SetEnvironment("noon", "light");
environment.SetEnvironment("night", "heavy");
// changing the scene in game, which will remove the observer and add new one
outdoorSceneRender.Remove();
IndoorSceneRender indoorSceneRender = new (environment);
// changing the environment
environment.SetEnvironment("morning", "dry");
Summery
The Observer Pattern defines a one-to-many dependency between objects so that when one object changes state, all of its dependents are notified and updated automatically yet the part of the code remains indepented of each other. There are variations of how this pattern is implemented, but the main concept is the same. If you are interested, you can give this a read.