What code smells indicate that a group of collaborating classes should be refactored to introduce a Mediator, and when would introducing one be the wrong call?
answer
- Sibling reference mesh
- Shotgun surgery for one rule
- Can't test a participant alone
- Re-entrancy flags spreading
- Wrong when: two objects, plain fan-out, bad decomposition
basics
~20 sIntroduce a mediator when classes hold references to many siblings, one rule change forces edits in several of them, and reuse is impossible because each object hard-references the others. Skip it when only two objects talk or reactions are independent broadcasts.
solid answer
~50 sSignals in favour: colleagues holding references to several siblings; a change to one interaction rule requiring shotgun edits across classes; classes that cannot be reused or unit-tested in isolation because they hard-reference specific peers; interaction rules that exist only implicitly, so nobody can state the protocol; and constructors/wiring that grow combinatorially as participants are added. Signals against: only two objects interact (direct calls are clearer); the reactions are independent, additive fan-out (plain Observer/events are lighter); the participants are really one badly split concept, where merging is the right fix; the interaction is a long-running business process better modelled by an explicit state machine or workflow engine; or the coordination is trivial and a mediator would add indirection without removing any rule. The deciding question is whether the *relationship* between the objects is the complicated part; if the complexity is inside each object rather than between them, Mediator does not help.
go deeper
Mention the obvious signals: classes referencing lots of siblings, and one small change forcing edits in many files.
List several smells (shotgun surgery, untestable participants, combinatorial wiring, re-entrancy flags) and at least two situations where a mediator is overkill.
Add the incremental refactoring path with tests, and the discriminating question — is the complexity between the objects or inside them.
Judge whether the decomposition itself is wrong, whether an explicit workflow/state machine is the right target instead, and weigh the long-term ownership cost of the coordinator you are creating.
## The smells that argue for a Mediator **1. Sibling reference sprawl.** A class holds fields pointing at several peers at the same layer that it exists only to notify. If you draw the object graph and see a mesh rather than a tree, coordination is leaking into participants. **2. Shotgun surgery on rules.** Changing one interaction rule ("clearing the state field should also clear postal code") forces edits in three or four files. The rule has no home, so it lives as fragments. **3. Unreusable, untestable participants.** You cannot instantiate a widget/component in a test without constructing its peers, because it calls them directly. Test setup balloons; reuse in another context is impossible. **4. The protocol is unstateable.** Nobody on the team can describe the full interaction without reading every participant. There is no file whose name matches the behaviour. **5. Combinatorial wiring.** Every new participant requires touching existing participants' constructors or registration code. Onboarding cost per participant grows with the number already present. **6. Notification cycles / re-entrancy bugs.** A calls B, B calls C, C calls A — you add re-entrancy guards or "suppress events" flags in several classes. Centralizing lets one place own ordering and cycle prevention. ## How the refactoring proceeds 1. Identify the participant set and the events they raise. 2. Introduce a mediator interface with a single reporting method (`notify(sender, event)` or typed messages). 3. Give each participant a mediator reference and replace its direct peer calls with a report to the mediator. 4. Move the rules — one at a time, with tests — into the mediator, verifying behaviour after each move. 5. Delete the now-unused peer references. Extract computation the mediator absorbed into services so it stays thin. Doing this incrementally is important: the intermediate state (participant reports *and* still calls a peer) is safe and shippable. ## When Mediator is the wrong call **Two participants only.** A direct call is more readable than an indirection through a coordinator. Mediator pays off with several participants and non-trivial rules. **Independent, additive reactions.** If a change should trigger logging, metrics, and cache invalidation — reactions that do not depend on each other or on ordering — plain Observer/domain events are lighter and keep the publisher agnostic. **The objects are one concept split badly.** If the "colleagues" always change together and cannot exist apart, the honest fix is to merge them or rebalance responsibilities, not to add a coordinator over a bad decomposition. Adding a mediator here cements the wrong boundaries. **Complexity is inside participants, not between them.** If each object has hairy internal logic but the wiring is simple, Mediator addresses the wrong axis; extract methods/strategies inside the objects instead. **A long-running, persistent process.** Multi-step, resumable, compensable flows deserve an explicit state machine, workflow engine, or saga — essentially a mediator with first-class states, persistence, and failure handling. An ad-hoc mediator with boolean flags will re-implement that badly. **Performance-critical hot paths.** The extra hop and dynamic dispatch are usually irrelevant, but in tight loops or latency-critical code the indirection may not be worth it. Measure before invoking this objection — it is far more often used as an excuse than as a real constraint. ## The deciding question Ask: *is the difficulty in what each object does, or in how they relate?* Mediator only helps with the second. And ask what a change costs today: if changing one rule means editing one class, you do not have the problem Mediator solves.
- How would you introduce a mediator into existing code without a risky big-bang rewrite?Incrementally and behind tests. Add the mediator interface and give each participant a reference, but move only one interaction rule at a time: the participant starts reporting to the mediator while its old direct call still exists, you verify, then delete the direct call. Each step is independently shippable. Characterize the current behaviour with tests first, since the protocol is implicit and easy to change accidentally, and extract any computation the mediator absorbs into services so it does not start life fat.
- Colleagues keep triggering each other in cycles. Does a mediator fix that, and how?It gives you a place to fix it, not an automatic fix. Because all notifications flow through one object, the mediator can own re-entrancy control — a dispatching flag, a queue that defers notifications raised during dispatch, or an explicit ordered pass over the rules — instead of every participant carrying its own suppression flag. If cycles are inherent to the rules rather than accidental, the mediator makes that visible, which is usually the prompt to model the flow as an explicit state machine.