Observer Design Pattern in JavaScript

1. Definition

The Observer Design Pattern defines a one-to-many dependency between objects so that when one object (subject) changes state, all its dependent objects (observers) are notified and updated automatically.

2. Problem Statement

Suppose you have an object (subject) and multiple observers. When the state of the subject changes, you want to inform all observers about this change, without having the subject explicitly call each observer. Additionally, the number of observers might be dynamic, meaning that they can be added or removed at runtime.

3. Solution

Implement the Observer pattern which separates the subject from its observers, allowing you to add or remove observers. The subject maintains a list of observers and provides methods to add or remove them. Whenever the subject's state changes, it notifies all registered observers.

4. Real-World Use Cases

1. News publishers and subscribers.

2. Event management system where events trigger multiple actions.

3. Model-View-Controller (MVC) architecture where model changes are reflected in the view.

5. Implementation Steps

1. Create a Subject class with methods to add, remove, and notify observers.

2. Create an Observer interface with an update method.

3. Concrete observers will implement the Observer interface.

4. Whenever the state of the subject changes, call the notify method to update all observers.

6. Implementation in JavaScript

// Observer Interface
class Observer {
    update() {
        // to be overridden by concrete observers
    }
}
// ConcreteObserver Class
class ConcreteObserver extends Observer {
    constructor(name) {
        super();
        this._name = name;
    }
    update(message) {
        console.log(`Observer ${this._name} received message: ${message}`);
    }
}
// Subject Class
class Subject {
    constructor() {
        this._observers = [];
    }
    addObserver(observer) {
        this._observers.push(observer);
    }
    removeObserver(observer) {
        const index = this._observers.indexOf(observer);
        if (index !== -1) {
            this._observers.splice(index, 1);
        }
    }
    notifyObservers(message) {
        for (let observer of this._observers) {
            observer.update(message);
        }
    }
}
// Client Code
const subject = new Subject();
const observer1 = new ConcreteObserver("A");
const observer2 = new ConcreteObserver("B");
subject.addObserver(observer1);
subject.addObserver(observer2);
subject.notifyObservers("Hello, Observers!");
subject.removeObserver(observer1);
subject.notifyObservers("Hello again, Observers!");

Output:

Observer A received message: Hello, Observers!
Observer B received message: Hello, Observers!
Observer B received message: Hello again, Observers!

Explanation:

1. We first define an Observer interface with an update method. This ensures all observers have a consistent interface.

2. The ConcreteObserver class implements the Observer interface. Each observer has a name to distinguish it in the output.

3. The Subject class maintains a list of observers. Observers can be added or removed. When the notifyObservers method is called, every registered observer's update method is invoked.

4. In the client code, we instantiate a subject and two observers. After adding both observers to the subject, we notify them. Then, we remove one observer and notify them again, demonstrating the dynamic nature of the pattern.

7. When to use?

The Observer Pattern is useful when:

1. The change of a state in one object must be reflected in another without keeping the objects tightly coupled.

2. An object should be able to notify other objects without making assumptions about those objects.

3. The number of objects that observe another object can vary dynamically.


Comments