Sitemap

C# Design Patterns: Observer

7 min readJan 22, 2024

--

Press enter or click to view image in full size
Observer C# Design Pattern

Thanks for visiting this article. Please scroll down for the bite-sized explanation of this pattern.

Definition

The Observer design pattern is a behavioural design pattern that creates a one-to-many dependency between an object and dependents, allowing the dependents to be notified when the state of the objects gets changed.

The object maintains a collection of observers and notifies them when the state change occurs. The dependents can register and unregister themselves from the object dynamically.

Components of the observer pattern

🤖 Subject — The object that is being observed. This object maintains a collection of the dependents, the observers.

👨‍👩‍👦‍👦 Observers — Represent the objects that are listening to the state changes of the subject.

Example

Let’s consider we are building a turn-based battle game, in which the Player and Enemies take turns to attack each other.

To keep track of the player stats, we have a class Player.cs with basic stats like Health, IsAttacking, and IsDead.

Press enter or click to view image in full size
Player class

The player class has methods to attack and receive damage ⚔️ from an enemy, and in the latter, the player can eventually die when its health reaches 0. ☠️

The code works, but the user can’t see much of what is happening on the screen.

Let’s fix this by adding a HealthUI class that can display the health stats on the screen.

Press enter or click to view image in full size
Player updating HealthUI directly

Great, now we can see how much health the player has, and based on that we can move the player away from enemy attacks to recover when the player is low on health.

But despite the health being visible we don’t see any animation when the player attacks or dies.

Luckily we have an animator class with public methods to play dead and attack animations.

Let’s hook our PlayerAnimator class into the Player class.

Press enter or click to view image in full size
Player updating HealthUI and PlayerAnimator directly

Awesome, with animation working the game is coming to life. But we still need something. 🤔

We can see the player attacking, taking damage, but … we don’t hear anything.

Ah…how can we forget? Audio plays a crucial role in any game. 😅 🔈.

Time to hook up the AudioManager class we have lying around, to play audio clips when the player attacks, receives damage and dies.

Press enter or click to view image in full size
The Player directly updates all dependents.

Cool, we got the audio, animations and UI working, the game now looks and sounds amazing.

But … do you see a problem with adding on stuff to the player class, as we keep coming up with functionalities we need? 🤔 💁🏻.

If <you do>,
Well done! … you are becoming a good developer.
Else,
read along … you are about to be.

Mainly 3 major issues with the design —

❌ Tight Coupling — The Player is now directly coupled with specific actions from different classes, which it shouldn’t know about. Any changes to these specific actions require a change in the Player class.

❌ Scalability — To add more components when a player state change occurs, we need to modify the Player class.

❌ Flexibility & Maintenance — As the number of components grows along with our game, it will be increasingly complex to maintain these direct connections and will grow our Player class unnecessarily longer.

To solve this we are going to use the Observer Design Pattern.

Implementing the Observer Pattern

There are many ways developers can design the implementation of the Observer pattern based on the requirements in their project.

1st Approach

If you read blogs regarding the Observer Design pattern, the common way most of them mentioned is to define an IObserver interface with an Update()/Notify() method.

Press enter or click to view image in full size
IObserver interface for notifying health changes.

Then implement this interface on the dependent classes, in our case HealthUI, we will see what happens to PlayerAnimator, and AudioManager a bit later with this approach.

Press enter or click to view image in full size
HealthUI implementing IObserver interface

Finally, update the Player to keep a collection of IObserver(s) and public methods to Register() and UnRegister() the concrete observers, to notify them when the health changes.

Press enter or click to view image in full size
IObserver approach

Well, the above implementation works for HealthUI as the UI is dependent on health changes, but what about the PlayerAnimator and AudioManager?

Press enter or click to view image in full size
PlayerAnimator and AudioManager are forced to implement IObserver

They don’t need to listen to the player’s health … but rather different player states i.e., IsAttacking, IsTakingDamage, IsDead.

Get Biswajit Sahoo’s stories in your inbox

Join Medium for free to get updates from this writer.

And, this is where the current approach… fails, and again creates a design issue similar to what the Bridge Pattern solves.

Read bridge pattern here — https://medium.com/@bsahoo1995/c-design-patterns-bridge-13e367b7c1e8

The Better Approach

In C#, an Action or Action<T> delegate lets us assign a method that can be executed using Action.Invoke().

See https://learn.microsoft.com/en-us/dotnet/api/system.action-1?view=net-8.0

The Action acts as a variable to which we can assign a method, matching the arguments, that can be executed later when the action gets invoked.

In the below, example we have an Action<bool> defined with a single “boolean” argument, which we want to invoke when a “something” happens in the ActionExample class.

And we want AnotherClass to react, when that “something” happens.

So we assign a method to the Action delegate so that when that “something” happens, we can let the AnotherClass know.

Press enter or click to view image in full size
Action example

But … Why can’t we use “Action” by itself?

Similar to any public member, the Action delegate if kept public, can be set and invoked by any other class having reference to it.

Imagine, someone else sending messages to your better-half from your phone, without you knowing. How terrible would that be ? 💔 😠

We only want the publisher class to send its event and still allow other dependents to subscribe to the events. How can we do it?

1️⃣ One way is … we can keep the action private, and provide public methods to subscribe and unsubscribe to the Action delegate, which will work.

💚 but there is another way we can declare an Action without the need to worry about someone else invoking it.

The “event” keyword.

See — https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/event

The event keyword is used to declare an event in a publisher class and is a special kind of multicast delegate that can only be invoked from within the class itself (or a derived class), or in a struct where they are declared.

With Action delegate, combined with the event keyword we can precisely define the needed actions, allowing dynamic subscription and un-subscription without any interface implementation using “+” and “-” operators, enhancing code readability.

Let’s see how we can use the Actions to solve the issue we had above.

First, we will remove the IObservers collection from the player and instead create three new events, HealthUpdated, Attacking and PlayerDead, and invoke them from respective methods.

Press enter or click to view image in full size
The Player define specific events and invoke the events

And all the dependents only have to subscribe and unsubscribe to the specific events they need, to listen and react to the state changes.

The HealthUI, subscribing to HealthUpdated event.

Press enter or click to view image in full size
HealthUI only subscribing to the Player’s HealthUpdated event

The PlayerAnimator subscribes to Attacking and PlayerDead events.

Press enter or click to view image in full size
PlayerAnimator only subscribes to the Player’s Attacking and PlayerDead event

And, AudioManager subscribing to all three events to play respective audio clips.

Press enter or click to view image in full size
AudioManager subscribes to all the Player’s HealthUpdated, Attacking and PlayerDead events

And that’s how we have used the Observer design pattern to 👇

✅ Decouple the observers (in our case the HealthUI, PlayerAnimator, AudioManager) from the subject (Player), reacting to player states as they change.

✅ Made a more scalable and flexible system where new observers can be added without modifying the subject, and the subject doesn’t need to know the specifics of its observers. 😃 ❤️

Things to keep in mind?

⚠️ It is extremely important always to unsubscribe the events when the observer instances go out of scope or are destroyed.

⚠️ Before invoking the events, always check for “null” as the Action delegate is just a wrapper for assigned methods. If nothing is assigned yet, you’ll get a NullReferenceException.

When to use?

A few use cases of the Observer pattern would be,

  1. One-to-Many Dependency:- When a change in one object should be reflected in other objects without making them tightly coupled.
  2. Decoupling Event Handling:- When we want to decouple the sender of a notification (subject) from its receivers (observers). Used mainly, in event handling systems.
  3. UI Components:- Commonly used in graphical user interface (GUI) systems where changes in one part of the interface (subject) need to be reflected in other parts (observers).

Hope this helped with giving you some clarity on how this pattern can be implemented and used in a real-world scenario.

On the last note. If you like the article please do like and share, and also feel free to provide your valuable comments and feedback.

--

--

Written by Biswajit Sahoo

Software dev who writes about C#, AI, Unity, and XR or anything trending in simpler words.