As developers, we’re always looking for ways to make our code more elegant, efficient, and maintainable. One way to achieve this is through the use of design patterns. In this blog post, we’ll look at the Observer design pattern in C#.
The Observer design pattern is a software design pattern in which an object, called the subject, maintains a list of its dependents, called observers. When the subject changes state, it notifies its observers. The Observer design pattern is useful when you have objects that need to be notified of changes in another object.
You can use the Observer design pattern to reduce coupling between objects and make your code more flexible. This article will show you the Observer design pattern, its uses, benefits, drawbacks, and more, with examples in C#. Stay tuned!
What is the Observer design pattern?
The design patterns provide the programmers with a solution to a recurring programming problem. They provide a way of solving a particular problem in the most sophisticated way. The Gang of Four (GoF) proposed the 23 design patterns to speed up the development process and provide well-tested development paradigms.
The Observer design pattern is a behavioral pattern that defines a one-to-many relationship between objects, such that one object (the subject) can notify other objects (the observers) of changes to its state. It is often used in event-driven programming, where an event can trigger a change in state in one or more other objects.
This pattern defines a one-to-many relationship, i.e., many objects depend upon a particular object. All the observers wait for a certain event to trigger. Whenever an event is triggered, all the observers catch it and make the relevant changes.
When to use the Observer design pattern?
Developers widely use the Observer design pattern, where many objects depend on a particular object. Here are some scenarios where you could use the Observer pattern:
- When other objects must reflect changes to one object.
- When there is a high chance of adding new observers in the future while keeping the code changes at a minimum.
- When you do not know the number of observers in advance, and they need to change dynamically.
Simply put, you can use the Observer pattern whenever there is a one-to-many relationship between objects, and you want to keep them loosely coupled.
How to implement the Observer pattern in C# – Structual code:
The following UML diagram depicts the high-level working of the Observer pattern.
In the above diagram, you can see the following four participants of the Observer pattern:
- Subject – Subject refers to the service which the observers will observe and any change in whose state will impact the observers. It is an interface or an abstract class with defined methods to add, remove and notify the subscribers.
- ConcreteSubject – The ConcreteSubject is responsible for keeping track of the Subject’s state and notifying the observers about any change in it.
- Observer – An interface that updates the status of observers.
- ConcreteObserver – Implements the Observer interface and changes the observers’ state with the Subject’s state.
Here is the structural code of the Observer design pattern.
public abstract class AbstractSubject
{
private List<AbstractObserver> observers = new();
public void Attach(AbstractObserver observer)
{
observers.Add(observer);
}
public void Detach(AbstractObserver observer)
{
observers.Remove(observer);
}
public void Notify()
{
foreach (AbstractObserver o in observers)
{
o.Update();
}
}
}
public class ConcreteSubject : AbstractSubject
{
public string SubjectState { get; set; } = string.Empty;
}
public interface AbstractObserver
{
public void Update();
}
public class ConcreteObserver : AbstractObserver
{
private string _observerName;
private string _observerState = string.Empty;
public ConcreteObserver(
ConcreteSubject observedObject, string observerName)
{
Subject = observedObject;
_observerName = observerName;
}
public void Update()
{
_observerState = Subject.SubjectState;
Console.WriteLine($"Observer: {_observerName}\nState Changed to {_observerState}");
}
public ConcreteSubject Subject { get; set; }
}
And the usage of the pattern.
// Configuring the Observer pattern
//Creating the subject
using ObserverPattern;
ConcreteSubject subject = new ConcreteSubject();
//Creating the observers that observe the above subject
ConcreteObserver obs1 = new ConcreteObserver(subject, "1");
ConcreteObserver obs2 = new ConcreteObserver(subject, "2");
ConcreteObserver obs3 = new ConcreteObserver(subject, "3");
//Adding observers in Observer's list
subject.Attach(obs1);
subject.Attach(obs2);
subject.Attach(obs3);
Console.WriteLine("Changing SubjectState");
//Changing the Subject State
//This will automatically change the Observers' state
subject.SubjectState = "New State";
//Notifying the Observers about changes in the subject's state
subject.Notify();
Console.ReadKey();
It produces the following output:
Observer pattern – Real-world example in C#
The following C# code shows the real-world scenario where several customers wait for the iPhones to be available at the store.
//Abstract Subject
public abstract class iPhoneStock
{
//list of customers waiting for iPhone
private List<iPhoneCustomer> _customers = new();
//adding a new customer
public void AddCustomer(iPhoneCustomer customer)
{
_customers.Add(customer);
}
//removing a customer
public void RemoveCustomer(iPhoneCustomer customer)
{
_customers.Remove(customer);
}
//notifying customers whenever the subject's state changes
public void Notify()
{
foreach (iPhoneCustomer iCustomer in _customers)
{
iCustomer.Update();
}
}
}
//Concrete Subject
public class iPhone : iPhoneStock
{
public string Availability { get; set; } = string.Empty;
}
//Abstract Observer
public interface iPhoneCustomer
{
public abstract void Update();
}
//Concrete Observer
public class Customer : iPhoneCustomer
{
private string _name;
private string _state = string.Empty;
private iPhone _iPhone;
public Customer(
iPhone Iphone, string customer_name)
{
_iPhone = Iphone;
_name = customer_name;
}
public void Update()
{
_state = _iPhone.Availability;
Console.WriteLine($"Customer: {_name}\nState Changed " +
$"to {_state} as iPhone is {_iPhone.Availability}");
}
}
And the usage:
var iphone = new iPhone();
//Creating the customers that observe the above subject
var customer1 = new Customer(iphone, "1");
var customer2 = new Customer(iphone, "2");
var customer3 = new Customer(iphone, "3");
//Adding customers in customer's list
iphone.AddCustomer(customer1);
iphone.AddCustomer(customer2);
iphone.AddCustomer(customer3);
//Changing the Subject State
//This will automatically change the customers' state
iphone.Availability = "Available";
//Notifying the customers about the availability of the iPhone
iphone.Notify();
Console.ReadKey();
produces the following output:
Benefits and drawbacks of using the Observer pattern
Some benefits of using the Observer pattern are:
- Enables the loose coupling between objects while they interact with each other.
- Allows you to add more subscribers without needing to change the Subject’s code, i.e., it supports the Open/Closed principle.
- It allows you to establish relationships among objects during runtime.
- Flexibility to add or remove the observers.
The downsides of using the Observer design pattern are:
- If you do not implement the Observer pattern efficiently, you may end up with complex and low-performing code with undesired output.
- It notifies observers in random order.
- It may cause memory leakage due to explicit addition and removal of the observers.
What are the related patterns to the Observer design pattern?
The Observer design pattern shares the same purpose as Command, Mediator, and Chain of Responsibility patterns. That means they all provide different ways to connect the publishers and subscribers of a message.
Here is how each works:
- Command pattern establishes a single-sided connection between publisher and subscriber.
- Mediator pattern provides an interface for the publisher and subscriber to communicate indirectly to avoid any direct communications. You can sometimes implement the Mediator and Observer design patterns interchangeably, as they somehow share a similar mechanism.
- Chain of Responsibility pattern passes the subscriber’s request to a dynamic chain of publishers unless one of them serves it.
Conclusion
Design patterns provide a versatile way to resolve many programming problems and make the developers’ lives easier. The 23 design patterns proposed by Gang of Four (GoF) are used extensively by programmers.
One of the widely used behavioral design patterns is the Observer, which provides a way to notify the observers about the status of a subject. It is highly implementable in publish/subscriber scenarios.
It is related to the Chain of Responsibility, Command, and Mediator patterns. It also provides an interface for the subscribers to stay updated about the service they have subscribed to.
Hope that you will now have a better understanding of the Observer design pattern after reading this article!
If you want to learn more about design patterns, check the separate in-depth article about design patterns in C#.