design·lab

Behavioral pattern

Observer Pattern

One subject holds state. Many observers want to react when it changes — without the subject knowing who they are. It just announces; they listen.

✗ The problem

Tight coupling to every listener

The subject hard-codes calls to each dependent. Add a listener → edit the subject.

class Stock {
  setPrice(p) {
    this.price = p;
    // subject must KNOW every consumer:
    chart.redraw(p);
    alerts.check(p);
    logger.record(p);   // ← new? edit here again…
  }
}
Every new consumer means reopening Stock. Sound familiar? It's the Open/Closed smell again.
✓ The pattern

Invert it: subject broadcasts, observers subscribe

The subject keeps a list of subscribers and just calls update() on each. It has no idea what they do.

class Subject {
  subs = [];

  subscribe(o)   { this.subs.push(o); }

  unsubscribe(o) {
    this.subs = this.subs.filter(s => s !== o);
  }

  notify(data) {
    this.subs.forEach(o => o.update(data));
  }
}
Subject
notify()
Observer A
Observer B
Observer C
✓ See it live

Publish an event — watch it ripple

Set a new stock price. The subject notifies every subscribed observer. Toggle observers on/off — the subject never changes.

📈 Stock  $100
📊
Chart
🔔
Alerts
🗒️
Logger
0 observers notified

Tip: click an observer to unsubscribe it, then publish again.

✓ Takeaway

Loose coupling by broadcast

  • Subject depends only on an update() contract — not on concrete observers.
  • Add / remove observers at runtime; the subject's code never changes.
  • You already use it: DOM addEventListener, RxJS, React state, pub/sub queues.
  • Careful: unsubscribe when done (memory leaks) and beware notification storms / ordering.
🎯 Principle applied: Observer buys loose coupling through Dependency Inversion (the subject depends only on the update() contract) and Open/Closed (add or remove subscribers without editing the subject).