Compare
Symptom
Human

A class hierarchy that varies along two independent axes (shape kind × rendering backend, document type × storage format, message type × transport) explodes into a cross-product of subclasses. Adding a new value on either axis multiplies the work; the class count tracks N×M instead of N+M.

Agent

Cross-product class hierarchies the agent must reason about as N×M cells. Editing one method's contract requires updating every cell; missing cells produce silent type-compatible inconsistencies the test suite may not catch until a customer hits an unexercised combination.

Goal
Human

Two hierarchies that compose: an abstraction holds a reference to its implementation and delegates the variable part. Adding a new shape is one class; adding a new renderer is one class; the cross-product is implicit in the composition, not enumerated as classes.

Agent

Two independent surfaces the agent reads separately. The abstraction's contract lives at one file; each implementation lives at one file; composition is structurally typed, and adding a new axis value is one new file the agent generates against the abstraction's interface.

Before the pattern

class CanvasCircle {
constructor(x, y, r) { this.x = x; this.y = y; this.r = r; }
draw(ctx) { ctx.beginPath(); ctx.arc(this.x, this.y, this.r, 0, 2 * Math.PI); ctx.stroke(); }
}
class SvgCircle {
constructor(x, y, r) { this.x = x; this.y = y; this.r = r; }
draw() { return `<circle cx="${this.x}" cy="${this.y}" r="${this.r}"/>`; }
}
class CanvasSquare {
constructor(x, y, side) { this.x = x; this.y = y; this.side = side; }
draw(ctx) { ctx.strokeRect(this.x, this.y, this.side, this.side); }
}
class SvgSquare {
constructor(x, y, side) { this.x = x; this.y = y; this.side = side; }
draw() { return `<rect x="${this.x}" y="${this.y}" width="${this.side}" height="${this.side}"/>`; }
}
// Adding Triangle = 2 new classes. Adding WebglRenderer = 3 new classes.
// Two shapes × two renderers = 4 classes; N × M scales combinatorially.

After the pattern

class CanvasRenderer {
drawCircle(x, y, r) { this.ctx.beginPath(); this.ctx.arc(x, y, r, 0, 2 * Math.PI); this.ctx.stroke(); }
drawSquare(x, y, side) { this.ctx.strokeRect(x, y, side, side); }
}
class SvgRenderer {
drawCircle(x, y, r) { return `<circle cx="${x}" cy="${y}" r="${r}"/>`; }
drawSquare(x, y, side) { return `<rect x="${x}" y="${y}" width="${side}" height="${side}"/>`; }
}
class Circle {
constructor(renderer, x, y, r) { this.renderer = renderer; this.x = x; this.y = y; this.r = r; }
draw() { return this.renderer.drawCircle(this.x, this.y, this.r); }
}
class Square {
constructor(renderer, x, y, side) { this.renderer = renderer; this.x = x; this.y = y; this.side = side; }
draw() { return this.renderer.drawSquare(this.x, this.y, this.side); }
}
// Adding Triangle = 1 new shape + 1 new method per renderer.
// Adding WebglRenderer = 1 new class. Two shapes + two renderers = 4 classes; N + M scales linearly.
Example source: Illustrative example written for this site in the spirit of Design Patterns (Gamma, Helm, Johnson, Vlissides, Addison-Wesley, 1994), chapter 4. The book's running example is a cross-platform Window hierarchy with XWindow / PMWindow implementations; this JavaScript adaptation uses Shape × Renderer to make the N+M vs N×M class-count payoff visible in code, not just in prose.
Pressure
Human

Every new variant on either axis is Shotgun Surgery: adding WebGL forces editing every shape class; adding Triangle forces editing every renderer class. The work scales multiplicatively; tests scale multiplicatively; review burden scales multiplicatively.

Agent

N×M cell verification on every cross-axis edit. The agent's reasoning load grows multiplicatively with both axes; static analysis of 'is this shape×renderer combination supported' requires enumerating the full cross-product, which is expensive in context budget.

Tradeoff
Human

Two hierarchies are harder to learn than one — the reader must understand both the abstraction interface and the implementation interface before the system makes sense. Composition introduces indirection: 'where does drawCircle actually run?' becomes a navigation step the reader did not have to make under inheritance.

Agent

Two-surface comprehension cost is higher per-read than a single hierarchy. The agent must hold the abstraction's expected interface and the implementation's actual interface in mind simultaneously; mismatches surface as runtime errors at the delegation site, not at compile time in older type systems.

Relief
Human

Class count and edit cost both drop from N×M to N+M. Each axis varies independently; new combinations require zero code. Tests for the renderer cover all shapes that delegate to it; tests for a shape cover its behaviour against any renderer.

Agent

Per-axis edits scope to one file; the type system enforces the contract; combination behaviour is testable as 'this shape × this renderer' without needing a new class. The agent's edit/verify cycle on adding an axis value is bounded and local.

Trap
Human

Bridge applied before the two-axis variation actually exists is Speculative Generality at its most expensive — two hierarchies and an indirection layer to support exactly one renderer. Wait for the second renderer (or second shape, or whichever axis varies) to ship before bridging.

Agent

Premature bridging doubles file count and adds indirection the agent navigates on every read, with zero benefit until the second axis variant ships. A bridge with one implementation signals the pattern was applied too early; the cost is paid on every read even if the second axis never ships.