Free Interactive Course · Design Patterns

Mediator Design Pattern

Stop objects wiring themselves to each other and put one switchboard in the middle — knowing exactly what that centralisation costs.

Behavioural Patternsmediuma.k.a. Controllera.k.a. Hub
ShareXLinkedIn
In one sentence

Define an object that encapsulates how a set of objects interact, keeping them from referring to each other explicitly and letting their interaction vary independently.

01

The problem Mediator solves

The problem

A flight booking form. Six controls, and every one of them has an opinion about the others.

Tick return trip and the return-date field must enable itself. Change the departure country and the currency selector must switch, the price must recalculate, and any promo code must be revalidated because some codes are region-locked. Pick business class and the promo field must clear and disable itself. Choose a return date before the departure date and the submit button must go grey.

Written the obvious way, the return-trip checkbox holds a reference to the date field. The country dropdown holds references to the currency selector, the price label and the promo field. The promo field needs to ask the class selector what it is. Six controls, and the wiring between them is approaching thirty references — every one of them a reason for one widget to change when another does.

Then design asks to reuse the promo field on the checkout page. You can't: it only compiles alongside a class selector and a country dropdown it has never needed to know about.

02

How the Mediator pattern works

Cut every wire between the widgets and give them all one wire to the middle:

  1. Each colleague keeps a reference to the mediator only — never to another colleague.
  2. When something happens, a colleague tells the mediator: "I changed." It doesn't say what should follow, because it doesn't know.
  3. The mediator holds the interaction rules — all of them, in one readable place — and updates whichever colleagues are affected.
  4. N² wires become N. Each widget becomes reusable on its own, and "what happens when the country changes" is answered by reading one method.
Be honest about the trade-off, because this pattern has a real one. You have not removed the complexity — you have relocated it. The interaction rules all now live in the mediator, so adding a seventh control means editing it, and a large screen can grow a mediator that is the biggest, most-edited class in the module. That is the accepted cost: many small, reusable, decoupled colleagues plus one deliberately complicated hub, instead of the coupling being smeared invisibly across all of them. It's a good trade right up until the hub becomes a god object — and knowing where that line is, is the whole skill.
BookingFormevery interaction ruleReturnTripCheckCountrySelectClassSelectReturnDatePromoFieldPriceLabel6 wires, not 30 — and every control compiles on its own
Participants. The Mediator defines how colleagues communicate; the Concrete Mediator (BookingForm) knows every colleague and implements the rules. Each Colleague knows only the mediator. Every arrow points inward — no colleague has an edge to another, which is the property that makes each of them reusable elsewhere.
03

See it: one change, the hub decides

The country dropdown changes. It doesn't know what should happen next — it just tells the form. Watch the mediator decide which controls are affected, and then add a control that didn't exist when the form was written.

▶ Try it — change the country

An interactive mediator: when the country selector changes it notifies BookingForm, which updates the currency selector, price label, promo field and submit button according to the rules it holds. Adding a new control means teaching the mediator about it — the pattern's central trade-off.

Detach PromoField and change the country again: everything else carries on, because no other control ever held a reference to it. That's the win. Then add TaxNotice and read the log carefully — the mediator had to learn a new rule. Compare that with Observer, where a new subscriber costs the subject nothing; the difference between the two patterns is exactly that line.
04

Mediator pattern code examples

Colleagues that only know the hub, and one place that holds every interaction rule.

public interface FormMediator {
    void changed(Control source);
}

/** A colleague. Note what it does NOT have: a reference to any other control. */
public final class CountrySelect extends Control {

    private final FormMediator form;
    private String value;

    public CountrySelect(FormMediator form) { this.form = form; }

    public void select(String country) {
        this.value = country;
        form.changed(this);          // "I changed." Not "update the price."
    }

    public String value() { return value; }
}

/** The mediator: every rule about how these controls relate, in one file. */
public final class BookingForm implements FormMediator {

    private final CountrySelect country = new CountrySelect(this);
    private final CurrencySelect currency = new CurrencySelect(this);
    private final PromoField promo = new PromoField(this);
    private final ClassSelect travelClass = new ClassSelect(this);
    private final PriceLabel price = new PriceLabel(this);

    @Override public void changed(Control source) {
        if (source == country) {
            currency.setTo(Currencies.forCountry(country.value()));
            promo.revalidateFor(country.value());
            price.recalculate();
        } else if (source == travelClass) {
            promo.setEnabled(!travelClass.isBusiness());
            price.recalculate();
        }
        // Every interaction rule is readable here, in order, in one method.
    }
}

// The if-chain on `source` is the usual smell as this grows. A Map<Control,
// Runnable> or one method per event (onCountryChanged, onClassChanged) keeps it
// readable — and if it still sprawls, that's the signal to split the screen.
Read across the tabs: the pattern needs no machinery — Go and JavaScript express it with a callback field and a rules object, and React's "lift state up" advice is this pattern handed out as a convention. Two things are worth taking away: the if (source == …) chain in the classic version is the smell that turns a mediator into a god object, and a table (Map, Record, dict) fixes it — TypeScript's Record<FormEvent, …> even makes a forgotten rule a compile error. And in C#, know that MediatR is not this pattern: it routes one request to one handler, which is closer to a Command dispatcher.
05

How to implement Mediator

  1. Identify the group of objects whose mutual references have become a web — usually a screen, a dialog, or a small set of collaborating services.
  2. Define the mediator interface with a single notification method: colleagues report what happened, never what should happen next.
  3. Give each colleague a reference to the mediator and remove every reference it holds to a sibling.
  4. Put the interaction rules in the mediator, keyed by event rather than in a growing if-chain on the source object.
  5. Keep the colleagues' back-reference non-owning — the mediator owns them, and a strong cycle back leaks.
  6. Guard against feedback loops: a rule that updates a colleague which then notifies the mediator again can recurse. Set a re-entrancy flag or make updates non-notifying.
  7. Watch the mediator's size. When it grows past comfortable reading, split the screen into two mediators rather than adding another method.
06

When to use Mediator — and when not to

Use it when a set of objects communicate in well-defined but complicated ways; when reusing an object is hard because it references half a dozen others; or when behaviour spread across several classes should be customisable without subclassing all of them. Dialogs, forms, wizards, and small clusters of collaborating services are the natural home.

Where it goes wrong

The god object. The single most common outcome. Every rule lands in the mediator, nobody deletes any, and eventually it's two thousand lines that every ticket touches. The complexity moved rather than vanished — watch its size like you'd watch a memory leak, and split by screen or feature before it hardens.

Feedback loops. The mediator updates a control, which reports that it changed, which triggers the rule again. Stack overflow, or a UI that flickers between two states. Use a re-entrancy guard or silent setters.

Everything routed through the hub. Not every interaction is the mediator's business. A control managing its own internal state doesn't need to notify anyone, and pushing trivia through the hub makes the hub the bottleneck people accuse it of being.

Mediator where you wanted an event bus. A mediator knows its colleagues by name and holds rules about them. If you just want anonymous broadcast to unknown listeners, you want Observer — a mediator with anonymous colleagues gives you a hub with none of the clarity.

You want to…UseBecause
Centralise the rules about how a known set of objects interactMediatorOne hub holds the rules; colleagues hold no references to each other.
Broadcast a change to unknown, interchangeable listenersObserverThe subject never learns who subscribes; a mediator knows everyone by name.
Give several handlers a chance at a request in orderChain of ResponsibilityCoR distributes the routing; Mediator centralises it.
Simplify a subsystem for outside callersFacadeA Facade's subsystem doesn't know it exists; a mediator's colleagues all do.
Turn an interaction into a storable, replayable objectCommandOften used together: colleagues send commands, the mediator dispatches them.
07

Quick check

🧠 Quick check
You add a seventh control to the booking form. What does that cost in a Mediator design, and what would it cost in an Observer design?

In the wild

JavaSwing and JavaFX dialog controllers; java.util.Timer coordinating scheduled tasks that never reference one another.
JavaScriptReact's "lift state up" — the common parent that owns the state and the rules for two siblings is a mediator, taught as a convention rather than a pattern.
C#A WPF view-model coordinating controls. Note that MediatR, despite the name, routes one request to one handler and is closer to a Command dispatcher.
PythonA Django form's clean() method, where cross-field validation rules live in one place instead of each field knowing the others.
C++Qt's signal/slot connections made in a parent widget's constructor — the parent decides which signal reaches which slot, and the children stay reusable.
GoA coordinator struct owning several workers and wiring them with callbacks or channels, so no worker imports another worker's package.

Frequently asked questions

What is the difference between the Mediator and Observer patterns?
Who knows whom. In Observer, the subject broadcasts "I changed" to subscribers it knows nothing about — it can't tell you what happens when it fires, and a new subscriber costs the subject nothing. In Mediator, the hub knows every colleague by name and holds the rules about how they relate, so you can read the whole interaction in one place, and a new colleague means editing the hub. Observer buys extensibility; Mediator buys legibility. Mediators are frequently built on top of observers, which is why the two get muddled.
Doesn't Mediator just move the complexity into one class?
Yes — and that's the deal, stated honestly. You exchange coupling that was invisible and spread across every colleague for coupling that is concentrated, named and readable in one file. That's usually a good trade: each colleague becomes independently testable and reusable, and "what happens when the country changes" has one answer instead of six. It stops being a good trade when the mediator grows past what a person can read; the fix is to split it by screen or feature, not to distribute the references back into the colleagues.
Is MediatR in .NET the Mediator pattern?
Not really, despite the name — and it's worth knowing the difference before an interview. MediatR is an in-process dispatcher that routes a request to a single handler, decoupling caller from callee. GoF's Mediator is a hub that knows a fixed set of colleagues and encapsulates the rules about how they interact with each other. MediatR is closer to a Command dispatcher or a service locator for handlers. Both are useful; they solve different problems.
How do I stop a Mediator becoming a god object?
Treat its size as a metric you actually watch. Scope each mediator to one screen, dialog or feature rather than an entire application. Keep the rules in a table keyed by event instead of a growing if-chain, so the shape stays visible. Push anything that is one colleague's own business back into that colleague. And when the file passes whatever your team's threshold is, split the screen into two coordinated mediators — the pattern degrades gracefully if you act early and very badly if you don't.
When should I use Mediator instead of just letting objects talk directly?
When the number of relationships, not the number of objects, is what's hurting. Three objects with two well-understood references between them are fine — a mediator there is pure overhead. Six objects with thirty references, where you can't reuse any of them and every change ripples, is the case the pattern was written for. A useful trigger: when you find yourself unable to write a unit test for one widget without constructing four others, the wires have gone too far.

Finish the handbook, earn the certificate

All 23 Gang-of-Four patterns, each with a simulator you can click and code in seven languages. Free, no signup.

See all 23 patterns →
Found this course useful? Share it.
ShareXLinkedIn

Comments

0

Join the conversation. Sign in to leave a comment — we'd love to hear your thoughts.