A subject (model, sensor, store) calls each of its consumers by name when its state changes. Adding a new consumer means editing the subject's constructor (to receive the consumer) and its mutator (to call it). The subject changes for unrelated reasons each time a new consumer ships.
The subject exposes subscribe(observer) and an internal observer list. State-change methods iterate the list and notify each observer through a uniform notify() interface. Adding a consumer is a subscribe call; the subject stays unedited.
Before the pattern
class TemperatureSensor {constructor(currentDisplay, chartDisplay, alertSystem) {this.currentDisplay = currentDisplay;this.chartDisplay = chartDisplay;this.alertSystem = alertSystem;this.temperature = 0;}setTemperature(t) {this.temperature = t;this.currentDisplay.update(t);this.chartDisplay.recordSample(t);if (t > 80) this.alertSystem.fire('high temperature');}}// Adding a mobile-push consumer or a CSV-logger consumer requires editing// the sensor's constructor and setTemperature in lockstep. Subject changes// for unrelated reasons; one consumer's bug forces a sensor redeploy.
After the pattern
class TemperatureSensor {constructor() {this.observers = [];this.temperature = 0;}subscribe(observer) {this.observers.push(observer);return () => {this.observers = this.observers.filter((o) => o !== observer);};}setTemperature(t) {this.temperature = t;for (const observer of this.observers) {observer.notify(t);}}}const sensor = new TemperatureSensor();sensor.subscribe({ notify: (t) => currentDisplay.update(t) });sensor.subscribe({ notify: (t) => chartDisplay.recordSample(t) });sensor.subscribe({ notify: (t) => { if (t > 80) alertSystem.fire('high temperature'); }});
Per-consumer wiring inside the subject is Shotgun Surgery on every consumer addition and Divergent Change on the subject. The subject's git history accumulates 'add X consumer' entries that have nothing to do with the subject's actual responsibilities (sensing, storing, computing).
Notification order is implicit in subscription order; observers that depend on each other for correct state (e.g., one updates a cache that another reads) become order-sensitive in a way the subject's API does not document. Memory leaks from forgotten unsubscribes are the classic Observer footgun.
Subject's responsibilities are pure subject-things (state + notification dispatch); consumers register from wherever; new consumers ship without subject edits; testing the subject covers notification dispatch once, consumers test independently.
Observers that mutate the subject during notify() (re-entrant state changes), or whose notify methods throw, produce subtle bugs in observer ordering and re-entrancy. The pattern's promise breaks down without explicit re-entrancy and error-handling discipline; document the rules or watch them get violated quietly.