12 December 2017
Introduction
- After exploring creational design patterns like Singleton and Factory, we now shift our focus to behavioral patterns. Behavioral design patterns are concerned with how objects communicate and interact with one another. These patterns help you create flexible systems where objects can change their behavior without affecting others.
- Today, we’ll dive into the Observer Pattern, one of the most commonly used behavioral patterns. It’s all about creating a system where an object (the subject) can notify multiple other objects (the observers) whenever there is a change in its state, allowing those objects to update accordingly.
- By the end of this post, you will understand how the Observer Pattern works and how to implement it in C#.
What Is the Observer Pattern?
- Definition: The Observer Pattern establishes a one-to-many dependency between objects. When one object (the subject) changes its state, all dependent objects (the observers) are notified and updated automatically.
- Use Case:
- Consider a weather station where multiple devices (smartphones, web apps, etc.) need to be updated whenever the weather changes. The Observer Pattern is perfect for this use case because it allows the weather station (subject) to notify all the devices (observers) whenever there is a change in the temperature or other weather data.
When to Use the Observer Pattern
- Use Cases:
- Event-Driven Applications: If you have an event-driven system where multiple parts of the system need to respond to state changes, the Observer Pattern can help achieve this with minimal coupling.
- Real-Time Data: In applications such as stock price tracking, chat apps, or live sports score updates, the Observer Pattern is an ideal choice to keep multiple clients synchronized with the latest data.
- GUI Frameworks: Many graphical user interface (GUI) frameworks use the Observer Pattern to update views whenever there’s a change in the underlying data model.
How the Observer Pattern Works
- The Observer Pattern consists of two key components:
- Subject: The object whose state changes. It maintains a list of its observers and notifies them when the state changes.
- Observers: The objects that want to be notified when the subject’s state changes. These objects subscribe to the subject and update themselves when they receive a notification.
- Importantly, the subject and observers are loosely coupled. The subject doesn’t need to know the specifics about the observers, and observers don’t need to know about the subject’s internal details. They only need to be aware that they will be notified when the subject’s state changes.
Implementing the Observer Pattern in C#
Let’s now implement a simple example of the Observer Pattern for a Weather Station. In this example, the weather station will notify multiple devices (observers) about temperature updates.
using System;
using System.Collections.Generic;
// Subject Interface
public interface IWeatherStation
{
void RegisterObserver(IWeatherDisplay observer);
void RemoveObserver(IWeatherDisplay observer);
void NotifyObservers();
}
// Concrete Subject Class
public class WeatherStation : IWeatherStation
{
private List<IWeatherDisplay> _observers = new List<IWeatherDisplay>();
private float _temperature;
public void RegisterObserver(IWeatherDisplay observer)
{
_observers.Add(observer);
}
public void RemoveObserver(IWeatherDisplay observer)
{
_observers.Remove(observer);
}
public void NotifyObservers()
{
foreach (var observer in _observers)
{
observer.Update(_temperature);
}
}
public void SetTemperature(float temperature)
{
_temperature = temperature;
NotifyObservers(); // Notify all observers about the new temperature
}
}
// Observer Interface
public interface IWeatherDisplay
{
void Update(float temperature);
}
// Concrete Observer Classes
public class PhoneDisplay : IWeatherDisplay
{
public void Update(float temperature)
{
Console.WriteLine($"Phone Display: Current temperature is {temperature}°C");
}
}
public class DesktopDisplay : IWeatherDisplay
{
public void Update(float temperature)
{
Console.WriteLine($"Desktop Display: Current temperature is {temperature}°C");
}
}
public class Program
{
public static void Main()
{
// Create the weather station (subject)
var weatherStation = new WeatherStation();
// Create observers
var phoneDisplay = new PhoneDisplay();
var desktopDisplay = new DesktopDisplay();
// Register the observers with the weather station
weatherStation.RegisterObserver(phoneDisplay);
weatherStation.RegisterObserver(desktopDisplay);
// Simulate a temperature change, which will notify all observers
weatherStation.SetTemperature(25.0f); // All displays will be updated
weatherStation.SetTemperature(30.0f); // All displays will be updated
}
}
Explanation of Code
- IWeatherStation Interface: This defines the
RegisterObserver()
,RemoveObserver()
, andNotifyObservers()
methods for managing observers and notifying them of changes in state. - WeatherStation Class: This is the concrete implementation of the subject. It maintains a list of observers and notifies them whenever the temperature changes.
- IWeatherDisplay Interface: This defines the
Update()
method that each observer must implement. This method is called to update the observer when the subject’s state changes. - PhoneDisplay and DesktopDisplay Classes: These are the concrete observers that implement the
Update()
method to display the current temperature when notified. - Main() Method: In this method, we create a
WeatherStation
(subject) and two displays (observers). When the temperature is updated, both observers are notified and theirUpdate()
methods are called automatically.
Best Practices for the Observer Pattern
- Loose Coupling: One of the biggest advantages of the Observer Pattern is that it allows for loose coupling between the subject and observers. The subject doesn’t need to know any specifics about the observers, and observers don’t need to know the internal details of the subject.
- Unsubscribe Properly: Be sure to call
RemoveObserver()
when an observer is no longer interested in receiving updates. Otherwise, you risk memory leaks or unnecessary updates. - Notify Only When Needed: To optimize performance, the subject should only notify observers when there is an actual change in state. Avoid notifying observers unnecessarily.
Conclusion
- The Observer Pattern is a powerful behavioral design pattern that helps manage one-to-many relationships between objects. It is ideal for event-driven systems and real-time applications, where multiple parts of the system need to be notified of state changes.
- By using this pattern, you can build decoupled systems where subjects and observers can evolve independently, making your application more flexible and maintainable.
- In the next post, we’ll explore another behavioral design pattern: the Strategy Pattern. Stay tuned!