Behavioral Patterns II: Chain, Template, Iterator, Mediator, Memento, Visitor

Six more behavioral patterns, from Express-style middleware chains to undo snapshots, each with a picture and a compact example.

1. Chain of Responsibility

📞 Analogy: Calling customer support. Level 1 tries to help; if they can't, they pass you to Level 2, then to a specialist. Each handler either handles the request or passes it on.

// Express middleware IS a chain of responsibility
app.use(requestLogger);     // log, then next()
app.use(rateLimiter);       // may stop the chain with 429
app.use(authenticate);      // may stop the chain with 401
app.get("/orders", listOrders);

function authenticate(req, res, next) {
  const user = verifyToken(req.headers.authorization);
  if (!user) return res.status(401).json({ error: "Unauthorized" }); // stop here
  req.user = user;
  next(); // pass along
}

A tiny chain from scratch:

const chain = (...handlers) => (request) => {
  const run = (i) => (i < handlers.length ? handlers[i](request, () => run(i + 1)) : undefined);
  return run(0);
};

const handle = chain(
  (req, next) => (req.amount < 100 ? "✅ auto-approved" : next()),
  (req, next) => (req.amount < 5000 ? "👔 manager approved" : next()),
  (req) => "🏦 needs director approval",
);

handle({ amount: 2500 }); // "👔 manager approved"
Drawing diagram…

2. Template Method

🍰 Analogy: Every cake recipe has the same skeleton: prepare → mix → bake → decorate. A chocolate cake and a carrot cake fill in the steps differently, but the order never changes.

class DataImporter {
  // The template: fixed order of steps
  async run(source) {
    const raw = await this.read(source);
    const records = this.parse(raw);
    const valid = records.filter((r) => this.validate(r));
    await this.save(valid);
    return { imported: valid.length, skipped: records.length - valid.length };
  }
  validate(record) { return true; } // default hook, can be overridden
  async save(records) { await db.insertMany(records); }
}

class CsvImporter extends DataImporter {
  read(path) { return fs.promises.readFile(path, "utf8"); }
  parse(text) { return text.split("\n").slice(1).map((line) => line.split(",")); }
}

class JsonImporter extends DataImporter {
  read(url) { return fetch(url).then((r) => r.text()); }
  parse(text) { return JSON.parse(text); }
  validate(record) { return Boolean(record.email); }
}
Drawing diagram…

You've used it: React class lifecycle methods, test frameworks (beforeEach → test → afterEach), NestJS lifecycle hooks.

💡 Modern alternative: pass the varying steps as functions (Strategy) instead of subclassing, i.e. composition over inheritance.


3. Iterator

📺 Analogy: A TV remote's "next channel" button. You don't need to know how channels are stored; you just press next.

JavaScript has the Iterator pattern built in: anything with [Symbol.iterator] works with for…of, spread and destructuring.

class Playlist {
  constructor(songs) { this.songs = songs; }

  *[Symbol.iterator]() {             // a generator makes iterators easy
    for (const song of this.songs) yield song;
  }

  *shuffled() {
    const copy = [...this.songs].sort(() => Math.random() - 0.5);
    yield* copy;
  }
}

const playlist = new Playlist(["Song A", "Song B", "Song C"]);
for (const song of playlist) console.log(song);
const [first] = playlist.shuffled();

Iterators are lazy: they produce values only when asked, which is great for huge or infinite data:

function* idGenerator() {
  let id = 1;
  while (true) yield id++;
}
const ids = idGenerator();
ids.next().value; // 1
ids.next().value; // 2

You've used it: arrays, Map, Set, for await (const chunk of stream), paginated API helpers.


4. Mediator

✈️ Analogy: Planes don't talk to each other to decide who lands first. They all talk to the control tower, which coordinates everyone.

Problem: many objects talking directly to each other become a tangled web.

Drawing diagram…
// A chat room is a mediator between users
class ChatRoom {
  #members = new Map();
  join(user) { this.#members.set(user.name, user); user.room = this; }
  send(from, to, text) {
    const target = this.#members.get(to);
    if (!target) return from.receive("system", `${to} is offline`);
    target.receive(from.name, text);
  }
}

class User {
  constructor(name) { this.name = name; }
  send(to, text) { this.room.send(this, to, text); }       // talks only to the room
  receive(from, text) { console.log(`${this.name} ← ${from}: ${text}`); }
}

You've used it: a Redux store between components, an event bus, an API gateway, an orchestrator in a microservice saga.


5. Memento

💾 Analogy: Save points in a video game. Before the boss fight you save; if you lose, you load the snapshot.

Problem: capture an object's state so it can be restored later, without exposing its internals.

class Editor {
  #content = "";
  #cursor = 0;

  type(text) { this.#content += text; this.#cursor = this.#content.length; }
  save() { return Object.freeze({ content: this.#content, cursor: this.#cursor }); } // memento
  restore(memento) { ({ content: this.#content, cursor: this.#cursor } = memento); }
  get content() { return this.#content; }
}

const editor = new Editor();
const history = [];

editor.type("Hello");
history.push(editor.save());   // 💾 snapshot
editor.type(" oops!!!");
editor.restore(history.pop()); // ⏪ back to "Hello"

You've used it: browser history, "Restore previous version" in Google Docs, Redux DevTools time travel, database transactions (rollback).

💡 Memento vs Command for undo: Memento stores snapshots (simple, memory-heavy). Command stores operations that know how to reverse themselves (lighter, more code).


6. Visitor

🧾 Analogy: A tax inspector visits a restaurant, a factory and a shop. Each business lets the inspector in, and the inspector applies the right rules to each. New inspectors (health, fire safety) can visit without changing the businesses.

Problem: add new operations to a set of object types without changing those types.

// Node types (rarely change)
const num = (value) => ({ type: "num", value });
const add = (left, right) => ({ type: "add", left, right });
const mul = (left, right) => ({ type: "mul", left, right });

// Visitors: new operations over the same structure
const evaluate = {
  num: (n) => n.value,
  add: (n, visit) => visit(n.left) + visit(n.right),
  mul: (n, visit) => visit(n.left) * visit(n.right),
};

const print = {
  num: (n) => String(n.value),
  add: (n, visit) => `(${visit(n.left)} + ${visit(n.right)})`,
  mul: (n, visit) => `${visit(n.left)} × ${visit(n.right)}`,
};

const walk = (visitor) => function visit(node) { return visitor[node.type](node, visit); };

const expression = add(num(2), mul(num(3), num(4)));
walk(evaluate)(expression); // 14
walk(print)(expression);    // "(2 + 3 × 4)"

You've used it: Babel plugins and ESLint rules (they visit AST nodes), compilers, document exporters (to PDF, to HTML).


Cheat sheet

PatternOne-linerEveryday example
Chain of ResponsibilityPass a request along until someone handles itSupport escalation, Express middleware
Template MethodFixed skeleton, customisable stepsCake recipe
IteratorWalk a collection without knowing its insides"Next channel" button
MediatorCentral hub instead of many-to-many linksAirport control tower
MementoSave and restore snapshotsGame save points
VisitorNew operations without changing the typesTax inspector

Key takeaways

  • Chain is the idea behind every middleware system.
  • Template Method fixes the order of steps; prefer passing functions in modern JS.
  • Iterator is built into JavaScript via Symbol.iterator and generators.
  • Mediator tames many-to-many communication.
  • Memento gives you undo by snapshot. Visitor powers compilers and linters.