Observer Pattern

Observer Pattern

Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.

Intent

The Observer pattern decouples the Subject (the source of state changes) from its Observers (the objects that react to those changes). The Subject maintains a list of Observers and notifies all of them when its state changes — without knowing which concrete Observer types exist. Observers register and deregister themselves at runtime. This is the foundation of event-driven and reactive programming: push notifications from a single source to multiple consumers without hard-wired dependencies.

When NOT to Use

  • The relationship is one-to-one — use a direct callback instead; Observer overhead is unnecessary
  • Observers must be notified in a guaranteed order — Observer provides no ordering guarantee across implementations
  • Tight coupling between Observer and Subject state is unavoidable anyway — Observer then adds indirection without the benefit of decoupling
  • Performance-critical inner loop — notification dispatch overhead per iteration may be unacceptable

When to Use

  • Changes to one object require updating others and you do not know how many there are at design time
  • An object should be able to notify dependents without knowing who they are (open-ended subscriber list)
  • Classic event listener and pub-sub scenarios: UI components reacting to model changes, reactive streams, domain event dispatch

How It Works

Four participants:

  • Subject — maintains a list of Observers (attach(), detach()); calls notify() when its state changes
  • ConcreteSubject — holds the actual state; calls notify() on every state change
  • Observer — interface (or abstract class) declaring update()
  • ConcreteObserver — implements update(); queries the Subject for state or uses state passed directly

Push vs pull variants:

  • Push: Subject passes its state as arguments in the update() call — Observers receive data immediately
  • Pull: Subject calls update() with no arguments; Observers query the Subject afterwards — more flexible, slightly less efficient

In the TypeScript/Node.js ecosystem: Node.js EventEmitter is the GoF Subject realised in the standard library. RxJS Observable is the reactive extension of the same concept — with composable operators and explicit subscription lifecycle management.

Sequence Diagram

sequenceDiagram
    participant Subject
    participant ObserverA
    participant ObserverB
    participant ObserverC

    ObserverA->>Subject: subscribe(this)
    ObserverB->>Subject: subscribe(this)
    ObserverC->>Subject: subscribe(this)

    Note over Subject: State changes

    Subject->>Subject: notify()
    Subject->>ObserverA: update(state)
    Subject->>ObserverB: update(state)
    Subject->>ObserverC: update(state)

    Note over ObserverA,ObserverC: All registered observers receive the broadcast

    ObserverB->>Subject: unsubscribe(this)

    Note over Subject: State changes again

    Subject->>Subject: notify()
    Subject->>ObserverA: update(state)
    Subject->>ObserverC: update(state)

    Note right of ObserverB: No longer notified

TypeScript Example

// Observer — TypeScript (GoF structure via EventEmitter)
// Source: GoF Design Patterns, p.293
import { EventEmitter } from 'events';
 
class Subject extends EventEmitter {
  private state = 0;
  setState(value: number) {
    this.state = value;
    this.emit('change', this.state);   // notify all registered observers
  }
}
 
// Observer = any listener registered via .on()
const subject = new Subject();
subject.on('change', (v) => console.log('Observer A:', v));
subject.setState(42);  // → "Observer A: 42"

RxJS Memory Leak Warning: An RxJS subscribe() call without cleanup holds a reference indefinitely. Unlike EventEmitter's imperative removeListener, RxJS subscriptions are not automatically cleaned up when the subscriber is garbage-collected. Always use takeUntil(destroy$) or explicit subscription.unsubscribe().

// Observer — TypeScript (RxJS subscription cleanup)
// Source: RxJS 7 docs — https://rxjs.dev/guide/subscription
import { Subject, takeUntil } from 'rxjs';
 
class MyComponent {
  private destroy$ = new Subject<void>();
 
  ngOnInit() {
    someObservable$
      .pipe(takeUntil(this.destroy$))
      .subscribe(value => this.handle(value));
  }
 
  ngOnDestroy() {
    this.destroy$.next();
    this.destroy$.complete();
  }
 
  // Alternative: explicit unsubscribe
  // private subscription = someObservable$.subscribe(...);
  // ngOnDestroy() { this.subscription.unsubscribe(); }
}

Java Example

// Observer — Java (custom Subject/Observer — do NOT use java.util.Observable)
// Source: GoF Design Patterns, p.293
// Note: java.util.Observable deprecated in Java 9 — use custom Subject or PropertyChangeListener
 
import java.util.ArrayList;
import java.util.List;
 
interface Observer {
    void update(int state);
}
 
class Subject {
    private int state;
    private List<Observer> observers = new ArrayList<>();
 
    public void addObserver(Observer o)    { observers.add(o); }
    public void removeObserver(Observer o) { observers.remove(o); }
 
    public void setState(int value) {
        this.state = value;
        notifyObservers();
    }
 
    private void notifyObservers() {
        for (Observer o : observers) { o.update(state); }
    }
}
 
class ConcreteObserver implements Observer {
    private final String name;
    ConcreteObserver(String name) { this.name = name; }
    public void update(int state) { System.out.println(name + " notified: " + state); }
}
 
// Usage:
// Subject subject = new Subject();
// subject.addObserver(new ConcreteObserver("A"));
// subject.setState(42);  // → "A notified: 42"

Lineage Forward

Observer → Domain Events → Event Sourcing → CQRS lineage:

The Observer pattern is the upstream ancestor of the Event-Driven lineage chain. In DDD, Domain-Events (created Phase 10) replace the Observer Subject/Observer coupling with explicit named events and a domain event dispatcher — decoupling producers from consumers further than raw Observer. The lineage continues: Domain Events → Event-Sourcing-Pattern (Phase 12) → CQRS-Pattern (Phase 12) → Choreography-Saga-Pattern (Phase 13). See GoF-Patterns-MOC for the full lineage chain overview.

PatternRelationship
Mediator-PatternObserver is broadcast one-to-many where Subject notifies all; Mediator is hub-and-spoke where colleagues communicate only via the Mediator and do not know each other — see Mediator-Pattern for detailed comparison
Domain-EventsDDD extension of Observer: named events with explicit dispatch replace the Subject/Observer coupling, adding semantic meaning to notifications (created Phase 10)
Command-PatternCommand encapsulates what to do (an invocation); Observer decides who reacts to a state change — orthogonal concerns that often compose (Command triggers state change; Observer broadcasts the result)
  • GraphQL-API-Design — GraphQL subscriptions are the API-layer expression of the Observer pattern; clients subscribe to the schema and receive server-pushed events on each publish
  • News-Feed-Design — celebrity accounts use a pull (observer) model at read time; the hybrid push/pull strategy treats celebrity accounts as observables that followers pull on demand rather than receiving pushed feed entries
  • Chat-System-Design — presence service uses a publish-subscribe model: user status changes are published; subscribed contacts receive updates — the Observer pattern applied to real-time presence tracking
  • State-Management-Patterns — The Observable/RxJS paradigm is the direct descendant of Observer: an RxJS Observable is a Subject that pushes notifications to subscribed Observers. NgRx Effects implement the Observer pattern for side-effect dispatch. Angular's async pipe subscribes and unsubscribes automatically, enforcing the Observer cleanup rule.
  • Change-Detection-and-Rendering-Engines — Angular Signals are a synchronous fine-grained variant of Observer: signal() is the Subject, effect() and computed() are Observers with automatic dependency tracking.

Sources

  • Gamma, Helm, Johnson, Vlissides — Design Patterns: Elements of Reusable Object-Oriented Software, Addison-Wesley, 1994, p.293
  • RxJS 7 docs — subscription lifecycle and cleanup: https://rxjs.dev/guide/subscription

Notes that link here: GoF-Patterns-MOC