Chapter 12: How Patterns Interact
Patterns rarely live alone in real systems. They form small ecosystems where one pattern supplies structure, another supplies variation, and a third supplies coordination. This chapter explores several of these pairings. Each section focuses on how two patterns strengthen each other, why the pairing appears so often, and how to shape the interaction so it stays clear and maintainable.
Command and Memento
Command turns actions into objects, while Memento captures state so those actions can be reversed. Together they create undo and redo systems that behave predictably. The pairing works because actions and state snapshots complement each other; one describes what happened and the other preserves what must be restored.
Coordinated Roles
A Command stores just enough information to perform or reverse an operation. A Memento stores the internal state of the receiver. When used together, the command triggers changes and the memento anchors the state before and after those changes. This keeps the undo logic separate from business logic.
Compact Example
This demonstration uses simple placeholders inside braces to keep the focus on flow rather than implementation details. The command knows what to call; the memento holds the values needed to roll back the operation.
class Editor {
constructor() { this.text = "" }
type(s) { this.text += s }
save() { return { text: this.text } } // memento
restore(m) { this.text = m.text }
}
class TypeCommand {
constructor(ed, s) { this.ed = ed; this.s = s }
execute() {
this.before = this.ed.save()
this.ed.type(this.s)
}
undo() { this.ed.restore(this.before) }
}
Composite and Visitor
Composite represents hierarchical structures and lets clients treat groups and leaves uniformly. Visitor adds new operations to those structures without editing the existing classes. The interaction works because Composite exposes the shape of the tree and Visitor supplies pluggable behavior for walking it.
Why They Pair Well
Composite offers clean traversal. Visitor offers clean dispatch based on node type. When they meet, the traversal becomes predictable and the operations stay modular. You can add analytics, printing, exporting, or validation by swapping visitors without touching the tree structure.
Compact Example
This sketch shows nodes that accept a visitor. Internals that are not relevant are represented with … so that the mechanics of the interaction stay visible.
class Leaf {
constructor(v) { this.v = v }
accept(vis) { return vis.visitLeaf(this) }
}
class Group {
constructor(children) { this.children = children }
accept(vis) { return vis.visitGroup(this) }
}
class SumVisitor {
visitLeaf(n) { return n.v }
visitGroup(g) {
return g.children.reduce((a, c) => a + c.accept(this), 0)
}
}
Observer and Mediator
Observer broadcasts updates to subscribers, while Mediator centralises coordination among participants. Together they create systems with clear communication boundaries. Observer handles event flow; Mediator handles decision flow. This pairing appears wherever many components must stay informed without developing direct dependencies.
Balancing Broadcast and Control
Observer ensures that changes propagate. Mediator ensures that reactions remain orderly. Used together, they prevent the uncontrolled fan out that pure Observer setups sometimes suffer. The mediator becomes a traffic officer that listens to events and decides what should happen next.
Compact Example
This example shows a subject that emits events through simple callbacks. A mediator receives the event and coordinates the next steps. The inner details are trimmed where not essential.
class Subject {
constructor() { this.subs = [] }
subscribe(f) { this.subs.push(f) }
setValue(v) {
this.value = v
this.subs.forEach(f => f(v))
}
}
class Mediator {
constructor() { this.count = 0 }
onUpdate(v) {
this.count += 1
// coordinate …
}
}
const s = new Subject()
const m = new Mediator()
s.subscribe(v => m.onUpdate(v))
Factory Method and Strategy
Factory Method selects which object to create, while Strategy selects how an object behaves. Combining them creates flexible systems where construction and behaviour vary independently. This pairing appears in configuration driven systems, plugin systems, and applications that support multiple execution modes.
Independent Axes Of Variation
The factory controls object type. The strategy controls behaviour. When you separate the two, you avoid nested conditionals that mix object selection with operational logic. The result is easier testing and a clearer mental model of the system.
Compact Example
This demonstration shows a factory that chooses between processors and a strategy that selects the algorithm each processor uses. The shapes are small so the flow remains clear, and placeholder content is marked with ….
class FastStrategy {
run(xs) { return xs.reduce((a, b) => a + b, 0) }
}
class SafeStrategy {
run(xs) { return xs.filter(x => x > 0).reduce((a, b) => a + b, 0) }
}
class Processor {
constructor(strat) { this.strat = strat }
process(xs) { return this.strat.run(xs) }
}
function makeProcessor(kind) {
if (kind === "fast") return new Processor(new FastStrategy())
return new Processor(new SafeStrategy())
}
// usage: makeProcessor("fast").process([1,2,3])