Hermes Wiki

Mediator Pattern

Concept

Mediator pulls the communication logic between a set of interacting objects out of those objects and into a single, dedicated coordinator. Instead of every object holding direct references to every other object it needs to talk to — an all-to-all web of dependencies that grows quadratically as objects are added — each object talks only to the mediator, and the mediator decides who else needs to know and what happens next. The individual objects (GoF calls them "colleagues") become simpler and more reusable because they no longer encode knowledge of each other's existence or behavior; all of that cross-cutting coordination logic lives in one place.

The canonical illustration is a GUI dialog: a checkbox, a dropdown, and a submit button that all need to react to each other's state (checking a box enables the dropdown, an invalid dropdown value disables submit) could wire direct listeners between each pair of widgets, but that becomes an unmanageable tangle as more widgets are added. A mediator object instead owns all of that cross-widget logic — each widget just notifies the mediator "I changed," and the mediator decides what else needs to update. The same shape recurs constantly in backend systems: an orchestrator service coordinating several downstream microservices so they don't call each other directly, a chatroom server relaying messages between clients that never connect to each other peer-to-peer, or a game engine's central event bus coordinating entities that would otherwise need direct references to every other entity type they can interact with.

Tradeoffs

Approach Benefit Cost
Mediator (centralized coordinator) Colleagues are decoupled from each other — no N×N reference web; cross-cutting coordination logic lives in one auditable place; individual colleagues are easier to test and reuse in isolation The mediator itself can become a god object if it accumulates unrelated coordination logic over time — the complexity doesn't disappear, it relocates
Direct peer-to-peer references between objects No extra indirection layer; simplest for a fixed, small (2-3 object) interaction Reference count grows roughly O(n²) as objects are added; any object's internal change can ripple through every other object that references it directly
Publish/subscribe event bus (no central coordination logic) Publishers and subscribers are fully decoupled, don't even need mediator to know about specific colleague types No single place enforces coordination rules (order, conditional logic) the way a mediator's logic can — pub/sub gives you decoupled delivery, not decoupled decision-making

Mediator and pub/sub are often confused because both remove direct references between objects, but they solve different problems: pub/sub decouples delivery (who receives an event) with no opinion on business rules, while Mediator explicitly centralizes the decision logic about what should happen in response to a colleague's change. A pub/sub bus with no subscriber-side coordination logic isn't really a Mediator; a Mediator that just blindly rebroadcasts every message to every colleague is really just a pub/sub bus wearing a different name.

When to use / when not to

  • Use when a set of objects need to coordinate but the coordination rules themselves — not just event delivery — are complex enough to deserve their own home: form-validation logic across multiple UI fields, orchestration logic across several backend services that must happen in a specific order or under specific conditions.
  • Especially valuable when the number of interacting objects would otherwise produce a direct-reference web that's already hard to reason about — three or more objects with genuine cross-dependencies is usually the threshold where a mediator starts paying for itself.
  • Don't use it for two objects with a simple, stable relationship — a direct reference is clearer and a mediator there is pure ceremony.
  • Watch for the mediator itself becoming a dumping ground for every unrelated cross-object concern in the system; if it's grown past coordinating one clearly-scoped interaction, that's a signal to split it, not to keep piling logic in.

Common pitfall

Building an orchestrator service (a legitimate Mediator at the service-architecture level) and then letting downstream services quietly start calling each other directly for "just this one case," because a direct call felt faster than routing through the orchestrator. Each of these exceptions reintroduces exactly the tangled dependency graph the mediator existed to prevent, and because they're added piecemeal under time pressure, they're rarely documented anywhere — the system's actual behavior no longer matches the orchestration diagram everyone assumes is authoritative. The fix isn't a technical one so much as a discipline one: every cross-service interaction goes through the orchestrator, full stop, or the mediator boundary should be formally redrawn rather than silently leaked through.

Engineering Lens

Mediator is worth naming explicitly in a design review because "who talks to whom" is one of the first things that gets tangled as a system grows, and it tangles quietly — no single commit looks wrong, but the cumulative reference graph becomes unreviewable. Pointing at a specific coordination point and saying "this is where cross-object interaction logic lives, and nothing talks directly outside of it" is a testable architectural claim, the same way a circuit breaker lets you name exactly what happens when a dependency goes down. The tradeoff to watch honestly in review isn't "should we centralize coordination" — usually yes, past a handful of interacting objects — it's whether the centralization point itself is staying scoped to one coordination concern, since an unscoped mediator just becomes the new god object under a more palatable name.

  • Command Pattern — both decouple senders from receivers, but Command wraps a single action as an object while Mediator centralizes coordination logic across several interacting objects

Sources

Hermes Wiki