Episode 9 — System Design / 9.5 — Behavioral Design Patterns

9.5 Quick Revision -- Behavioral Design Patterns

Master Comparison Table

PatternIntentKey MetaphorJavaScript ExampleCore Mechanism
ObserverNotify many when one changesNewsletter subscribersEventEmitter, addEventListenerSubject maintains observer list, calls update()
StrategySwap algorithms at runtimeChoosing a GPS routePayment methods, sort algorithmsContext delegates to interchangeable strategy objects
CommandEncapsulate action as objectRestaurant order slipUndo/redo, task queuesCommand object with execute() and undo()
IteratorSequential access to collectionTV remote (next channel)for...of, generatorsnext() returns { value, done }
StateBehavior changes with stateTraffic lightOrder workflow, vending machineContext delegates to current state object
Template MethodFixed skeleton, variable stepsRecipe templateData processing pipelinesBase class calls abstract methods that subclasses override
Chain of ResponsibilityPass request along handler chainCorporate approval chainExpress middlewareEach handler processes or passes to next
MediatorCentralized communication hubAir traffic control towerChat rooms, Redux storeAll objects communicate through mediator only

When-to-Use Decision Guide

What problem are you solving?
|
+-- "Multiple objects need to react when something changes"
|   --> OBSERVER
|
+-- "I need to swap between different algorithms"
|   --> STRATEGY
|
+-- "I need to undo actions or queue operations"
|   --> COMMAND
|
+-- "I need to traverse a collection without exposing its internals"
|   --> ITERATOR
|
+-- "Object behavior changes based on its current state"
|   --> STATE
|
+-- "Several classes share the same algorithm structure but differ in steps"
|   --> TEMPLATE METHOD
|
+-- "A request should be handled by one of several handlers"
|   --> CHAIN OF RESPONSIBILITY
|
+-- "Multiple objects have complex many-to-many interactions"
|   --> MEDIATOR

Pattern Relationship Map

+------------------------------------------------------------------+
|                                                                    |
|  Strategy <-- similar --> State                                   |
|  (client swaps)          (auto-transitions)                       |
|                                                                    |
|  Observer <-- complements --> Mediator                            |
|  (1-to-many broadcast)       (centralized coordination)          |
|                                                                    |
|  Command <-- uses --> Iterator                                    |
|  (action objects)     (traverse command queue)                    |
|                                                                    |
|  Template Method <-- alternative --> Strategy                     |
|  (inheritance)                       (composition)                |
|                                                                    |
|  Chain of Resp. <-- used in --> Mediator                          |
|  (sequential handling)          (centralized routing)             |
|                                                                    |
+------------------------------------------------------------------+

Code Snippet Cheat Sheet

Observer -- Minimal Implementation

class EventEmitter {
  constructor() { this.events = new Map(); }

  on(event, fn) {
    if (!this.events.has(event)) this.events.set(event, new Set());
    this.events.get(event).add(fn);
    return () => this.events.get(event).delete(fn); // unsubscribe
  }

  emit(event, ...args) {
    this.events.get(event)?.forEach(fn => fn(...args));
  }
}

// Usage
const emitter = new EventEmitter();
const unsub = emitter.on('data', (d) => console.log(d));
emitter.emit('data', { name: 'test' });
unsub(); // cleanup

Strategy -- Function-Based

const strategies = {
  add: (a, b) => a + b,
  multiply: (a, b) => a * b,
  power: (a, b) => a ** b,
};

class Calculator {
  constructor(strategy = 'add') { this.strategy = strategies[strategy]; }
  setStrategy(name) { this.strategy = strategies[name]; return this; }
  execute(a, b) { return this.strategy(a, b); }
}

const calc = new Calculator();
calc.setStrategy('multiply').execute(5, 3); // 15

Command -- Undo/Redo

class Editor {
  constructor() { this.content = ''; this.undoStack = []; this.redoStack = []; }

  execute(cmd) {
    cmd.execute();
    this.undoStack.push(cmd);
    this.redoStack = [];
  }

  undo() {
    const cmd = this.undoStack.pop();
    if (cmd) { cmd.undo(); this.redoStack.push(cmd); }
  }

  redo() {
    const cmd = this.redoStack.pop();
    if (cmd) { cmd.execute(); this.undoStack.push(cmd); }
  }
}

class InsertCommand {
  constructor(editor, text) { this.editor = editor; this.text = text; }
  execute() { this.editor.content += this.text; }
  undo() { this.editor.content = this.editor.content.slice(0, -this.text.length); }
}

Iterator -- Generator

function* range(start, end, step = 1) {
  for (let i = start; i <= end; i += step) yield i;
}

// Use with for...of
for (const n of range(1, 10, 2)) console.log(n); // 1, 3, 5, 7, 9

// Use with spread
const nums = [...range(1, 5)]; // [1, 2, 3, 4, 5]

// Infinite generator
function* fibonacci() {
  let [a, b] = [0, 1];
  while (true) { yield a; [a, b] = [b, a + b]; }
}

State -- Order Workflow

class OrderState {
  constructor(order) { this.order = order; }
  pay()    { throw new Error(`Cannot pay in ${this.name}`); }
  ship()   { throw new Error(`Cannot ship in ${this.name}`); }
  cancel() { throw new Error(`Cannot cancel in ${this.name}`); }
}

class PendingState extends OrderState {
  get name() { return 'PENDING'; }
  pay()    { this.order.setState(new PaidState(this.order)); }
  cancel() { this.order.setState(new CancelledState(this.order)); }
}

class PaidState extends OrderState {
  get name() { return 'PAID'; }
  ship()   { this.order.setState(new ShippedState(this.order)); }
}

class Order {
  constructor() { this.state = new PendingState(this); }
  setState(s) { this.state = s; console.log(`-> ${s.name}`); }
  pay()    { this.state.pay(); }
  ship()   { this.state.ship(); }
  cancel() { this.state.cancel(); }
}

Template Method -- Processing Pipeline

class DataProcessor {
  process(input) {            // Template method (don't override)
    const parsed = this.parse(input);
    const filtered = this.shouldFilter() ? this.filter(parsed) : parsed;
    return this.format(filtered);
  }
  parse(input) { throw new Error('Abstract'); }
  filter(data) { return data; }           // Default: no-op
  format(data) { throw new Error('Abstract'); }
  shouldFilter() { return true; }          // Hook
}

class JSONProcessor extends DataProcessor {
  parse(input) { return JSON.parse(input); }
  filter(data) { return data.filter(d => d.active); }
  format(data) { return JSON.stringify(data, null, 2); }
}

Chain of Responsibility -- Middleware

class Handler {
  constructor() { this.next = null; }
  setNext(h) { this.next = h; return h; }
  handle(req) { return this.next?.handle(req); }
}

class AuthHandler extends Handler {
  handle(req) {
    if (!req.token) return { status: 401, body: 'Unauthorized' };
    req.user = { id: 1 }; // decoded from token
    return super.handle(req);
  }
}

class LogHandler extends Handler {
  handle(req) {
    console.log(`${req.method} ${req.path}`);
    return super.handle(req);
  }
}

// Build chain
const log = new LogHandler();
const auth = new AuthHandler();
log.setNext(auth);
log.handle({ method: 'GET', path: '/api', token: 'abc' });

Mediator -- Chat Room

class ChatRoom {
  constructor() { this.users = new Map(); }

  join(user) {
    this.users.set(user.name, user);
    user.room = this;
  }

  send(message, from) {
    for (const [name, user] of this.users) {
      if (name !== from.name) user.receive(message, from.name);
    }
  }

  dm(message, from, toName) {
    this.users.get(toName)?.receive(`(DM) ${message}`, from.name);
  }
}

class User {
  constructor(name) { this.name = name; this.room = null; }
  send(msg) { this.room.send(msg, this); }
  sendTo(msg, to) { this.room.dm(msg, this, to); }
  receive(msg, from) { console.log(`[${this.name}] ${from}: ${msg}`); }
}

Common Pitfalls Quick Reference

PatternPitfallFix
ObserverMemory leaks from forgotten subscriptionsAlways return and call unsubscribe functions
ObserverNotification stormsBatch updates, debounce, or throttle
StrategyOver-engineering simple if/elseUse Strategy only when 3+ algorithms exist or will grow
CommandNot storing enough info for undoEach command must capture full before-state
CommandRedo stack not cleared on new actionClear redo stack in execute()
IteratorConsuming an iterator twiceIterators are single-use; create a new one each time
StateGod state class with all logicEach state should only know its own behavior and transitions
StateForgetting invalid transitionsThrow errors for invalid actions, don't silently ignore
Template MethodToo many abstract methodsKeep abstract methods minimal; use hooks for optional steps
Chain of Resp.No default handler at endAlways have a fallback handler that catches unhandled requests
Chain of Resp.Order-dependent bugsDocument handler order; test chains with each handler first/last
MediatorGod Object mediatorKeep mediator focused on coordination, not business logic

Quick Pattern Selection Matrix

NeedPattern(s)
React to changesObserver
Swap algorithmsStrategy
Undo/redo actionsCommand
Traverse collectionsIterator
State-dependent behaviorState
Same structure, different detailsTemplate Method
Sequential handler processingChain of Responsibility
Reduce object couplingMediator
Queue/schedule actionsCommand + Iterator
Event-driven architectureObserver + Mediator
Plugin/middleware systemChain of Resp. + Strategy
Workflow engineState + Command
Form/UI coordinationMediator + Observer

One-Line Summaries for Each Pattern

PatternOne-Liner
Observer"When X changes, automatically notify A, B, and C."
Strategy"Same task, different algorithms -- swap them freely."
Command"Wrap actions as objects so you can queue, log, or undo them."
Iterator"Step through any collection without knowing its internals."
State"Object acts differently depending on what state it's in."
Template Method"Define the recipe steps; let subclasses fill in the details."
Chain of Resp."Pass the request down a chain until someone handles it."
Mediator"Everyone talks to the hub instead of to each other."

Key JavaScript Connections

PatternBuilt-in JavaScript / Framework Feature
ObserveraddEventListener, EventEmitter, MutationObserver, RxJS, React useEffect
StrategyHigher-order functions, Array.sort(compareFn), Express route handlers
CommandrequestAnimationFrame callbacks, setTimeout, Promise chains
IteratorSymbol.iterator, for...of, generators (function*), Array.from()
StateReact useReducer, XState, Redux reducers
Template MethodReact class component lifecycle (componentDidMount, render)
Chain of Resp.Express app.use() middleware, Koa middleware, fetch interceptors
MediatorRedux store, Vuex store, Angular services, Socket.io rooms