In the realm of software design, one pattern stands out for its ability to facilitate robust communication and interaction between components: the Observer Pattern. This pattern is a cornerstone of many successful software architectures, allowing developers to create decoupled, flexible systems that react dynamically to changes. In this blog post, we’ll delve into the essence of the Observer Pattern, its key components, benefits, and real-world applications.

Overview

The Observer Pattern is a behavioural design pattern that establishes a one-to-many relationship between objects. In this relationship, when one object (the subject) changes state, all dependent objects (the observers) are notified and updated automatically. This enables efficient communication and synchronisation between components without tightly coupling them together.

If, like me, you’ve worked with https://rxjs.dev/ you’ll have dealt with subjects and observers.

Key Components

  1. Subject: The subject is the entity that holds the data of interest. It maintains a list of observers and provides methods to attach, detach, and notify observers.
  2. Observer: Observers are the entities interested in the state changes of the subject. They implement an interface containing an update method, which the subject calls to notify them of changes.

How it works

  1. The subject maintains a collection of observers, often using an array-like data structure (lists!).
  2. If you’re an observer and you’d like to get on that list, you’d register with the subject.
  3. When the subject undergoes a state change, it calls an update method on each of the observers.
  4. The observers can respond to the subject by retrieving the new state and affecting changes based on that.

Benefits

  1. Loose Coupling: The Observer Pattern promotes loose coupling between objects. Observers are not tightly bound to the subject; they can be added or removed without affecting other parts of the system.
  2. Modularity and Reusability: Since components are decoupled, they can be reused in different contexts and configurations.
  3. Dynamic Updates: Observers are automatically notified of changes, enabling real-time updates and ensuring that all relevant components stay in sync.
  4. Scalability: The pattern facilitates the addition of new observers without modifying existing code, making the system more scalable and adaptable.

Real-World Applications

  1. User Interfaces: The Observer Pattern is widely used in GUI frameworks to handle events like button clicks, mouse movements, and keyboard inputs. UI elements act as observers that react to user interactions. Think of Angular and React, these apply the pattern extensively.
  2. Stock Market Monitoring: In financial applications, the pattern can be employed to keep investors updated on stock price changes. The subject represents stock prices, and various observers represent investors’ portfolios.
  3. Publish-Subscribe Systems: Systems that involve publishing messages and allowing multiple subscribers to receive those messages can utilise the Observer Pattern to ensure efficient distribution.

Implementing the pattern

Step 1 - Define the Subject interface or class with methods for attaching, detaching, and notifying observers.

public interface IObserver
{
    void Update(string message);
}

Step 2 - Implement the Observer interface or class with the update method.

public interface ISubject
{
    void Attach(IObserver observer);
    void Detach(IObserver observer);
    void NotifyObservers(string message);
}

Step 3 - Create concrete classes for both subjects and observers.

public class ConcreteObserver : IObserver
{
    private string _name;

    public ConcreteObserver(string name)
    {
        _name = name;
    }

    public void Update(string message)
    {
        Console.WriteLine($"Observer {_name} received message: {message}");
    }
}

Step 4 - Instantiate subjects and observers and establish the necessary relationships.

public class ConcreteSubject : ISubject
{
    private List<IObserver> _observers = new List<IObserver>();
    private string _state;

    public string State
    {
        get { return _state; }
        set
        {
            _state = value;
            NotifyObservers($"State changed to {_state}");
        }
    }

    public void Attach(IObserver observer)
    {
        _observers.Add(observer);
    }

    public void Detach(IObserver observer)
    {
        _observers.Remove(observer);
    }

    public void NotifyObservers(string message)
    {
        foreach (var observer in _observers)
        {
            observer.Update(message);
        }
    }
}

Step 5 - When the subject’s state changes, call the notify method to trigger the update method on all observers.

class Program
{
    static void Main(string[] args)
    {
        ConcreteSubject subject = new ConcreteSubject();

        ConcreteObserver observer1 = new ConcreteObserver("Observer 1");
        ConcreteObserver observer2 = new ConcreteObserver("Observer 2");

        subject.Attach(observer1);
        subject.Attach(observer2);

        subject.State = "State 1"; // This triggers notification to observers

        subject.Detach(observer1);

        subject.State = "State 2"; // Only observer2 will be notified

        Console.ReadLine();
    }
}

In this example, the ConcreteSubject maintains a list of observers and notifies them when its state changes. The ConcreteObserver implements the IObserver interface and defines the Update method to react to state changes.

The Main method demonstrates how to create and use observers and subjects. Observers are attached to the subject, and when the subject’s state changes, observers are notified and respond accordingly.

Feel free to run the provided code in a C# environment to see the Observer Pattern in action!

Conclusion

The Observer Pattern is a fundamental tool in a developer’s toolbox, enabling effective communication between components and fostering flexibility in software architectures. By allowing objects to react to changes without direct dependencies, it enhances modularity, scalability, and reusability. Whether in user interfaces, financial applications, or distributed systems, the Observer Pattern proves its worth by facilitating real-time interactions and dynamic updates in various domains.

In tribute - I wrote a not-dissimilar post to one of our cats.