Design Patterns: Observer

The Observer Pattern is a behavioral design pattern that facilitates the establishment of a one-to-many dependency between objects, where multiple observers are notified automatically of any state changes in a subject.

Design Patterns: Observer

Problem

In software development, there are scenarios where objects need to communicate with each other in a loosely coupled manner. This means that when one object's state changes, other dependent objects must be notified and updated accordingly. For instance, consider a scenario where multiple objects in a system need to be informed whenever a particular object's state changes. Directly coupling these objects together would make the system rigid and difficult to maintain.

Solution

The Observer Design Pattern addresses this problem by establishing a one-to-many dependency between objects, such that when the state of one object changes, all its dependents are notified and updated automatically. This pattern promotes loose coupling between objects, enabling them to interact without having direct knowledge of each other. In essence, it facilitates a publish-subscribe mechanism where the subject (or publisher) maintains a list of its dependents (observers or subscribers) and notifies them of any state changes.

In implementing the Observer Pattern, the initial step involves defining a Subject Interface or Abstract Class. This interface or abstract class serves as the blueprint, outlining the essential operations required for managing observers. These operations typically include methods for attaching observers to the subject, detaching observers when necessary, and notifying observers of any relevant state changes. By establishing this common interface, concrete subject classes can adhere to a consistent set of behaviors, ensuring interoperability with various observer implementations. This foundational aspect of the pattern facilitates the establishment of a flexible and cohesive communication mechanism between subjects and observers within the software system.

class diagram


The Concrete Subject Class is a crucial component in implementing the Observer Pattern. By implementing the Subject interface or abstract class, this class establishes a framework for managing observers. It maintains a collection of observers and offers methods for adding or removing them dynamically. Whenever the subject's state changes, it notifies all registered observers by invoking their update method, ensuring that they stay synchronized with the subject's state.

In tandem with the Concrete Subject Class, the Observer Interface or Abstract Class defines the contract for observers. It declares the update method, which subjects call to notify observers of any changes in their state. This interface ensures uniformity across various observer implementations.

Concrete Observer Classes are the concrete implementations of the Observer interface or abstract class. They register themselves with the subject to receive notifications and define the actions to be taken upon receiving updates. By encapsulating the logic for responding to state changes within these classes, the system maintains flexibility and modularity.

sequence diagram

Pros

  • Loose Coupling: The Observer Pattern promotes loose coupling between objects. Subjects and observers are independent entities and do not need to have direct knowledge of each other.
  • Scalability: It allows for easily adding or removing observers without modifying the subject. This makes the system more scalable and flexible.
  • Modularity: The pattern helps in dividing the system into smaller, more manageable pieces. Each observer can focus on a specific aspect of the subject's state.
  • Event Handling: It provides an efficient way to handle events or changes in the system. Observers are notified only when relevant changes occur, reducing unnecessary processing.
  • Support for Broadcast Communication: The Observer Pattern supports the broadcast communication paradigm, where a single event can trigger multiple reactions across the system.

Cons

  • Unexpected Updates: Observers might receive updates when they are not needed or not expected. This can lead to inefficiencies if observers need to filter out irrelevant updates.
  • Complexity: Implementing the Observer Pattern can introduce additional complexity to the system, especially when dealing with a large number of observers or complex notification logic.
  • Potential Memory Leaks: In languages without garbage collection or if references are not properly managed, there is a risk of memory leaks if observers are not detached from the subject when they are no longer needed.
  • Ordering of Notifications: The order in which observers are notified may not be guaranteed, which could lead to issues if the sequence of updates is important.
  • Debugging and Maintenance: Understanding and debugging systems using the Observer Pattern may be more challenging due to the indirect communication between objects.

Implementation

In the implementation section of the Observer Pattern, we delve into the practical aspects of applying this design pattern in real-world scenarios. This section provides insights into the steps involved in setting up a robust observer system, from defining the subject and observer interfaces to creating concrete implementations.

JAVA

interface Observer {
    void update();
}

interface Subject {
    void attach(Observer observer);
    void detach(Observer observer);
    void notifyObservers();
}

class ConcreteSubject implements Subject {
    private List<Observer> observers = new ArrayList<>();
    private int state;

    public void attach(Observer observer) {
        observers.add(observer);
    }

    public void detach(Observer observer) {
        observers.remove(observer);
    }

    public void notifyObservers() {
        for (Observer observer : observers) {
            observer.update();
        }
    }

    public void setState(int state) {
        this.state = state;
        notifyObservers();
    }
}

class ConcreteObserver implements Observer {
    private ConcreteSubject subject;

    public ConcreteObserver(ConcreteSubject subject) {
        this.subject = subject;
        this.subject.attach(this);
    }

    public void update() {
        System.out.println("Observer updated with new state");
    }
}

The provided code exemplifies the Observer Design Pattern implementation. It comprises an Observer interface, defining an update() method for concrete observers to implement. Additionally, a Subject interface is defined, offering methods for attaching, detaching, and notifying observers. The ConcreteSubject class implements the Subject interface, maintaining a list of observers and a state variable. When its state changes, it notifies all attached observers. The ConcreteObserver class, implementing the Observer interface, holds a reference to the subject it observes and updates itself upon notification.

ConcreteSubject subject = new ConcreteSubject();
ConcreteObserver observer = new ConcreteObserver(subject);
subject.setState(5);

The provided code snippet showcases the utilization of the Observer Design Pattern. It begins by instantiating a ConcreteSubject object named subject, which acts as the subject being observed. Subsequently, a ConcreteObserver object named observer is created, specifying the subject instance to observe upon instantiation. This step establishes the observer-subject relationship, where the observer is attached to the subject upon creation. Finally, the setState(5) method is invoked on the subject object, signaling a state change. As a result, the ConcreteSubject instance notifies all attached observers, including observer, triggering their respective update logic. This sequence of actions demonstrates the dynamic interaction enabled by the Observer Design Pattern, facilitating decoupled communication between subjects and observers in a software system.

C++

class Observer {
public:
    virtual ~Observer() = default;
    virtual void update() = 0;
};

class Subject {
public:
    virtual ~Subject() = default;
    virtual void attach(Observer* observer) = 0;
    virtual void detach(Observer* observer) = 0;
    virtual void notifyObservers() = 0;
};


class ConcreteSubject : public Subject {
private:
    std::vector<Observer*> observers;
    int state;

public:
    void attach(Observer* observer) override {
        observers.push_back(observer);
    }

    void detach(Observer* observer) override {
        observers.erase(std::remove(observers.begin(), observers.end(), observer), observers.end());
    }

    void notifyObservers() override {
        for (Observer* observer : observers) {
            observer->update();
        }
    }

    void setState(int state) {
        this->state = state;
        notifyObservers();
    }
};

class ConcreteObserver : public Observer {
private:
    ConcreteSubject& subject;

public:
    ConcreteObserver(ConcreteSubject& subject) : subject(subject) {
        subject.attach(this);
    }

    void update() override {
        std::cout << "Observer updated with new state" << std::endl;
    }
};

The provided code exemplifies the Observer Design Pattern implementation in C++. It begins by defining abstract base classes, Observer and Subject, which establish interfaces for concrete observers and subjects to adhere to. The ConcreteSubject class implements the Subject interface, maintaining a collection of observer pointers and a state variable. It overrides methods to attach, detach, and notify observers, ensuring they are updated upon state changes. Conversely, the ConcreteObserver class inherits from Observer, representing concrete observer implementations. Upon instantiation, it attaches itself to the subject and defines the update() method to handle state change notifications

ConcreteSubject subject;
ConcreteObserver observer(subject);

subject.setState(5);

The provided code demonstrates the utilization of the Observer Design Pattern. It initiates by creating a ConcreteSubject object named subject, representing the entity that will be observed for state changes. Subsequently, a ConcreteObserver object named observer is instantiated, with the subject instance passed to its constructor. This action establishes the observer-subject relationship, where the observer is automatically attached to the subject upon creation. Finally, the setState(5) method is invoked on the subject object, signifying a change in its state. Consequently, the subject notifies all attached observers, including the observer instance, prompting them to execute their update logic. Through this sequence of actions, the Observer Pattern facilitates dynamic interaction between subjects and observers, enabling robust and responsive behavior in software systems.

Python

class Observer:
    def update(self) -> None:
        pass


class Subject:
    def __init__(self):
        self.__state = 0
        self.__observers = []

    def attach(self, observer: Observer) -> None:
        self.__observers.append(observer)

    def detach(self, observer: Observer) -> None:
        self.__observers.remove(observer)

    def notify_observers(self) -> None:
        for observer in self.__observers:
            observer.update()

    def set_state(self, state: int) -> None:
        self.__state = state
        self.notify_observers()


class ConcreteObserver(Observer):
    def __init__(self, subject: Subject) -> None:
        super().__init__()
        self.__subject = subject


    def update(self) -> None:
        print("Observer updated with new state", self.__subject)

The provided code snippet illustrates the implementation of the Observer Design Pattern in Python. It defines an Observer base class with an update() method, acting as a template for concrete observer implementations. Additionally, a Subject class is introduced, managing a state variable and a list of observers. It provides methods to attach, detach, and notify observers upon state changes. Furthermore, a ConcreteObserver subclass inherits from Observer, holding a reference to the subject it observes. Upon notification, the update() method of concrete observers, such as ConcreteObserver, is invoked, facilitating customized handling of state changes.

subject = Subject()
observer = ConcreteObserver(subject)
subject.attach(observer)

subject.set_state(5)

The above code begins by instantiating a Subject object named subject, representing the entity that will be observed for state changes. Subsequently, a ConcreteObserver object named observer is created, with the subject instance passed to its constructor. This establishes the observer-subject relationship, where the observer is attached to the subject. Finally, the set_state(5) method is invoked on the subject object, indicating a change in its state. Consequently, the subject notifies all attached observers, including the observer instance, triggering their respective update logic. Through this sequence of actions, the Observer Pattern facilitates dynamic interaction between subjects and observers, enabling the implementation of responsive and flexible software systems.

Summary

The Observer Pattern facilitates loosely coupled communication between objects by establishing a one-to-many dependency relationship. This design pattern enables subjects to notify multiple observers of state changes without requiring direct knowledge of their identities. By decoupling the subject and observer, the pattern promotes modularity, scalability, and flexibility within software systems. Through its event-driven architecture, the Observer Pattern simplifies the handling of dynamic changes and fosters better organization and maintainability of code. As a fundamental building block of many software architectures, mastering the Observer Pattern empowers developers to create robust and adaptable systems capable of responding effectively to changing requirements and environments.