TypeScript Observer Pattern Example

1. Definition

The Observer Design Pattern defines a dependency between objects so that when one object changes its state, all its dependents are notified and updated automatically. It involves a subject and multiple observers. Observers "subscribe" to the subject to receive updates.

2. Problem Statement

Consider a situation where a state change in one object should trigger changes in other objects, without the main object knowing about these other dependent objects. Coding such relationships directly introduces tight coupling, which isn't good for scalability and maintenance.

3. Solution

The Observer pattern offers a solution by allowing objects (observers) to subscribe and unsubscribe from a subject to receive notifications about changes in the subject's state. This ensures that the subject doesn't need to know anything about its observers.

4. Real-World Use Cases

1. News subscription where subscribers get notifications when a new article is published.

2. Stock market tickers where investors get updates on stock prices.

3. GUI elements that react to changes in application data.

5. Implementation Steps

1. Define the Observer interface that will be implemented by all Observers.

2. Define the Subject interface for registering, deregistering, and notifying observers.

3. Create concrete classes for both Observer and Subject.

6. Implementation in TypeScript

// Step 1: Observer interface
interface Observer {
    update(message: string): void;
}
// Step 2: Subject interface
interface Subject {
    registerObserver(o: Observer): void;
    removeObserver(o: Observer): void;
    notifyObservers(): void;
}
// Concrete Subject class
class ConcreteSubject implements Subject {
    private observers: Observer[] = [];
    private message: string = '';
    registerObserver(o: Observer): void {
        this.observers.push(o);
    }
    removeObserver(o: Observer): void {
        const index = this.observers.indexOf(o);
        if (index !== -1) {
            this.observers.splice(index, 1);
        }
    }
    notifyObservers(): void {
        for (let o of this.observers) {
            o.update(this.message);
        }
    }
    setMessage(message: string): void {
        this.message = message;
        this.notifyObservers();
    }
}
// Concrete Observer class
class ConcreteObserver implements Observer {
    private name: string;
    constructor(name: string) {
        this.name = name;
    }
    update(message: string): void {
        console.log(`Observer ${this.name} received message: ${message}`);
    }
}
// Client code
const subject = new ConcreteSubject();
const observer1 = new ConcreteObserver("A");
const observer2 = new ConcreteObserver("B");
subject.registerObserver(observer1);
subject.registerObserver(observer2);
subject.setMessage("Hello Observers!");

Output:

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

Explanation:

The ConcreteSubject has a list of observers. Whenever setMessage is called on the subject, it updates its message and notifies its observers. In our client code, two observers (A and B) are registered to the subject. When a message is set on the subject, both observers receive and print it.

7. When to use?

Use the Observer pattern when:

1. A change in one object's state requires a change in other objects, and the actual number of objects is unknown or can change dynamically.

2. An object should be able to notify other objects without knowing who or what those objects are.


Comments