9.5 Quick Revision -- Behavioral Design Patterns
Master Comparison Table
| Pattern | Intent | Key Metaphor | JavaScript Example | Core Mechanism |
|---|
| Observer | Notify many when one changes | Newsletter subscribers | EventEmitter, addEventListener | Subject maintains observer list, calls update() |
| Strategy | Swap algorithms at runtime | Choosing a GPS route | Payment methods, sort algorithms | Context delegates to interchangeable strategy objects |
| Command | Encapsulate action as object | Restaurant order slip | Undo/redo, task queues | Command object with execute() and undo() |
| Iterator | Sequential access to collection | TV remote (next channel) | for...of, generators | next() returns { value, done } |
| State | Behavior changes with state | Traffic light | Order workflow, vending machine | Context delegates to current state object |
| Template Method | Fixed skeleton, variable steps | Recipe template | Data processing pipelines | Base class calls abstract methods that subclasses override |
| Chain of Responsibility | Pass request along handler chain | Corporate approval chain | Express middleware | Each handler processes or passes to next |
| Mediator | Centralized communication hub | Air traffic control tower | Chat rooms, Redux store | All 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);
}
emit(event, ...args) {
this.events.get(event)?.forEach(fn => fn(...args));
}
}
const emitter = new EventEmitter();
const unsub = emitter.on('data', (d) => console.log(d));
emitter.emit('data', { name: 'test' });
unsub();
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);
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;
}
for (const n of range(1, 10, 2)) console.log(n);
const nums = [...range(1, 5)];
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) {
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; }
format(data) { throw new Error('Abstract'); }
shouldFilter() { return true; }
}
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 };
return super.handle(req);
}
}
class LogHandler extends Handler {
handle(req) {
console.log(`${req.method} ${req.path}`);
return super.handle(req);
}
}
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
| Pattern | Pitfall | Fix |
|---|
| Observer | Memory leaks from forgotten subscriptions | Always return and call unsubscribe functions |
| Observer | Notification storms | Batch updates, debounce, or throttle |
| Strategy | Over-engineering simple if/else | Use Strategy only when 3+ algorithms exist or will grow |
| Command | Not storing enough info for undo | Each command must capture full before-state |
| Command | Redo stack not cleared on new action | Clear redo stack in execute() |
| Iterator | Consuming an iterator twice | Iterators are single-use; create a new one each time |
| State | God state class with all logic | Each state should only know its own behavior and transitions |
| State | Forgetting invalid transitions | Throw errors for invalid actions, don't silently ignore |
| Template Method | Too many abstract methods | Keep abstract methods minimal; use hooks for optional steps |
| Chain of Resp. | No default handler at end | Always have a fallback handler that catches unhandled requests |
| Chain of Resp. | Order-dependent bugs | Document handler order; test chains with each handler first/last |
| Mediator | God Object mediator | Keep mediator focused on coordination, not business logic |
Quick Pattern Selection Matrix
| Need | Pattern(s) |
|---|
| React to changes | Observer |
| Swap algorithms | Strategy |
| Undo/redo actions | Command |
| Traverse collections | Iterator |
| State-dependent behavior | State |
| Same structure, different details | Template Method |
| Sequential handler processing | Chain of Responsibility |
| Reduce object coupling | Mediator |
| Queue/schedule actions | Command + Iterator |
| Event-driven architecture | Observer + Mediator |
| Plugin/middleware system | Chain of Resp. + Strategy |
| Workflow engine | State + Command |
| Form/UI coordination | Mediator + Observer |
One-Line Summaries for Each Pattern
| Pattern | One-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
| Pattern | Built-in JavaScript / Framework Feature |
|---|
| Observer | addEventListener, EventEmitter, MutationObserver, RxJS, React useEffect |
| Strategy | Higher-order functions, Array.sort(compareFn), Express route handlers |
| Command | requestAnimationFrame callbacks, setTimeout, Promise chains |
| Iterator | Symbol.iterator, for...of, generators (function*), Array.from() |
| State | React useReducer, XState, Redux reducers |
| Template Method | React class component lifecycle (componentDidMount, render) |
| Chain of Resp. | Express app.use() middleware, Koa middleware, fetch interceptors |
| Mediator | Redux store, Vuex store, Angular services, Socket.io rooms |