design·lab

Real-world combination

Event Bus = Observer + Mediator

A single hub (Mediator) lets modules subscribe and react to named events (Observer) without ever knowing about each other.

✗ The problem

Every module wired to every other module

App-wide events — login, cart change, theme switch — need to reach many unrelated modules. Wire them directly and you get a tangled many-to-many mess.

function login(user) {
  navbar.greet(user);
  cart.sync(user);
  analytics.track('login', user);
  chat.connect(user);  // ← new consumer? edit here too…
}
Every module must know about every other module. Add one, and you edit every call site that touches it — a combinatorial explosion of dependencies.
✓ The combination

One hub, subscribed by name

A central Event Bus is a Mediator — the one hub everything talks to — that uses Observer semantics: subscribe and broadcast by event name, not by direct reference.

class EventBus {
  handlers = {};

  on(event, fn) {
    (this.handlers[event] ??= []).push(fn);
  }

  off(event, fn) {
    this.handlers[event] =
      (this.handlers[event] || []).filter(h => h !== fn);
  }

  emit(event, data) {
    (this.handlers[event] || []).forEach(fn => fn(data));
  }
}
// bus.on('login', fn); bus.emit('login', user);
Module A
Module B
EventBus
on / emit / off
Subscriber(s) of that event
✓ See it live

Emit an event — only subscribers react

Navbar listens for login, Cart listens for addToCart, Analytics listens for both. Emit an event and watch the bus fan it out to only the subscribed modules.

🔌 EventBus
↓ fan-out to subscribers ↓
🧭
Navbar
on('login')
🛒
Cart
on('addToCart')
📈
Analytics
on('login', 'addToCart')
no events emitted yet

Tip: click a module to unsubscribe it, then emit again.

✓ Takeaway

Decoupled by event name

  • Decoupling: modules only know event names, never each other's identity or API.
  • Named channels: 'login', 'addToCart' — any module can subscribe or emit.
  • Easy add/remove: new listeners plug in with on(); no existing code changes.
  • Caution: global events are harder to trace/debug, ordering isn't guaranteed, and forgetting off() leaks memory.
  • Real uses: Node's EventEmitter, browser CustomEvent/DOM events, Redux middleware, Vue/React app-wide event buses.
🎯 Combines: Mediator (a single hub decouples modules) + Observer (subscribe/emit by event) — often implemented as a Singleton app-wide.