Observer Pattern
Observer is behavioral design pattern that allows objects to subscribe to events occurring in another object and automatically receive notifications about changes in its state. Object sending messages is called subject, and objects subscribing to its notifications — observers.
Pattern Idea:
"Observer" simplifies coordination and maintaining consistency between several objects, allowing one object (Subject) to notify others (Observers) about its changes without hard binding to their implementation.
When to Apply "Observer"?
1. Feedback Needed (callbacks)
When one object must inform others about its changes, but shouldn't know details of their implementation (low coupling principle).
2. Multiple Dependencies
When there are several dependent objects (observers) that must react to changes in one object (subject).
3. Dynamic Interaction
When number of observers isn't fixed at compile time, and should change "on the fly".
How Does "Observer" Work?
-
Subject Interface
Describes methods for subscribing (attach) and unsubscribing (detach) observers, as well as for notifying (notify). -
Observer Interface
Containsupdate()method that's called by subject. -
Concrete Subject
Stores state that observers can track, and implements subscribe/unsubscribe methods. When state changes callsnotify(). -
Concrete Observer
Reacts to notifications by getting needed data from subject.
Example (TypeScript)
// 1. Observer interface
interface Observer {
update(subject: Subject): void;
}
// 2. Subject interface
interface Subject {
attach(observer: Observer): void;
detach(observer: Observer): void;
notify(): void;
}
// 3. Concrete subject
class ConcreteSubject implements Subject {
private observers: Observer[] = [];
private state: number = 0;
public attach(observer: Observer): void {
this.observers.push(observer);
}
public detach(observer: Observer): void {
const index = this.observers.indexOf(observer);
if (index !== -1) {
this.observers.splice(index, 1);
}
}
public notify(): void {
for (const observer of this.observers) {
observer.update(this);
}
}
// Example state change
public setState(value: number) {
this.state = value;
console.log(`Subject: state changed to ${this.state}`);
this.notify();
}
public getState(): number {
return this.state;
}
}
// 4. Concrete observers
class ConcreteObserverA implements Observer {
public update(subject: Subject): void {
if (subject instanceof ConcreteSubject && subject.getState() < 5) {
console.log("ConcreteObserverA reacts, as state < 5");
}
}
}
class ConcreteObserverB implements Observer {
public update(subject: Subject): void {
if (subject instanceof ConcreteSubject && subject.getState() >= 5) {
console.log("ConcreteObserverB reacts, as state >= 5");
}
}
}
// Usage example
function clientCode() {
const subject = new ConcreteSubject();
const observerA = new ConcreteObserverA();
const observerB = new ConcreteObserverB();
subject.attach(observerA);
subject.attach(observerB);
// Change state
subject.setState(3);
subject.setState(7);
subject.detach(observerA);
subject.setState(2); // Now only observerB tracks
}
clientCode();
- ConcreteSubject stores state and notifies all subscribers when it changes.
- ConcreteObserverA and ConcreteObserverB react to changes in their own way by polling subject.
Advantages
- Low coupling: Subject doesn't know observer details, only calls their update() method.
- Flexibility: Can dynamically add, remove and switch observers.
- Easy to extend: New observer types are added without changing subject.
Disadvantages
- Notification cascade: Error or excessive notification can lead to "chain reaction" and complicate debugging.
- Notification order: If different observers have dependencies on each other, order of calling update() may matter.
Important:
Observer pattern can be potentially dangerous with large number of subscribers or complex dependencies. Carefully track what events are sent and where, to avoid turning project into "spaghetti" code.
When to Use
- When need to centrally process events and notify several interested objects.
- When need to update objects when one source changes without direct dependency (e.g., subscription/notification system).
- When it's important that new modules can subscribe to events without changing already existing code.
Source
Observer pattern simplifies implementing reactive applications and reduces coupling between components. It's widely used in frontend (e.g., in Flux/Redux architecture, where subscribers react to store state changes). However, be careful with potential complexity growth with large number of subscribers and intersecting events.
Read more about Observer here.
- refactoring.guru - patterns and refactoring
- patterns.dev - patterns