In one sentence: Mediator puts a hub in the middle so components stop talking to each other directly and only talk to the hub.
Real-world analogy
Thirty aircraft are converging on one airport, and not one of them radios another to negotiate who lands first. Every pilot talks to the tower, and only to the tower: "Descend to 4,000, you're number three for runway 27."
The tower is the only party holding the whole picture — spacing, fuel states, the closed taxiway, the storm cell to the west. The rules about who goes when live up there, not in any cockpit. That's why a new arrival costs nothing: it checks in on the same frequency and the tower folds it into the sequence. No other aircraft changes its procedures, because no other aircraft knew about it in the first place.
The price is visible if you look up at the tower. All the complexity that isn't in the cockpits has to be somewhere, and it's in that room. Pilots have a simple job because the controller has a very hard one, and when the controller is overloaded there is nothing any single pilot can do about it.
Mapping the analogy to the pattern:
- Each aircraft → a Colleague: it knows the tower and nothing whatsoever about its peers.
- The control tower → the ConcreteMediator, which knows and maintains its colleagues and decides who gets told what.
- Radioing the tower and only the tower → colleagues hold a reference to the Mediator alone; plane-to-plane links simply don't exist, so ten aircraft need ten links, not forty-five.
- The tower's sequencing rules → the interaction logic, centralised in one object you can read top to bottom instead of scattered through every colleague.
- The overloaded controller → GoF's stated liability: the mediator absorbs all the complexity it spared the colleagues, and can grow into a monolith and a bottleneck.
Another angle: a theatre's stage manager. Actors, lighting, and sound never cue each other — everyone takes their cue over one headset, so re-blocking a scene means rewriting the calls, not retraining the cast.
The problem
You have a chat app where every participant must see every message. The direct approach is to give each participant a list of the others:
- The connections explode. Every new member has to be wired to every existing one. Ten members means forty-five links, all of which must be kept in sync as people join and leave.
- Nobody can be reused or tested alone. A member class that holds pointers to five other member classes can't be dropped into another program, and testing it means constructing the whole crowd.
The solution
Introduce a mediator that owns the relationships:
- Components — GoF calls them colleagues — no longer store references to each other. Each one stores a reference to the mediator only.
- When something happens, a colleague notifies the mediator. The mediator decides who else needs to know and tells them.
- All the coordination rules — who hears what, in what order, under what condition — live in one class you can read top to bottom.
The wiring turns from a mesh into a star. Adding a component means one link, not N. GoF's running example is a font dialog: pick a name in the list box and the entry field updates, which in turn enables buttons that were greyed out. Those rules aren't a property of list boxes or buttons — they're a property of this dialog, so they belong in one object that owns the dialog's behaviour.
Note: Two of GoF's implementation points are worth stealing. First, you can skip the abstract
Mediatorbase class when the colleagues only ever work with one mediator — the abstraction only earns its keep if you need to swap mediator implementations, which is why the example below has justChatRoom. Second, on how a colleague reaches the mediator: one option is to implement the mediator as an observer and the colleagues as subjects; another is a purpose-built notification method where the colleague passes itself as an argument so the mediator can tell who called. That second approach is whatbroadcast(*this, text)does below.
Structure
Three participants, and note which arrows are missing — no colleague points at another colleague:
- Mediator — defines the interface for communicating with colleague objects. Optional when there is only one mediator, as in the example below.
- ConcreteMediator — implements the cooperative behaviour by coordinating colleagues, and it knows and maintains its colleagues. Here:
ChatRoom, which owns the member list and decides who receives what. - Colleague — each colleague knows its mediator and nothing about its peers. Whenever it would otherwise have talked to another colleague, it talks to the mediator instead. Here:
MemberandBot, which hold aChatRoom&and nothing else.
C++ example
A chat room where members broadcast through the room and never hold a pointer to each other. The room owns its members, so everyone dies with the room.
#include <iostream>
#include <memory>
#include <string>
#include <vector>
class ChatRoom; // the mediator, defined just below
class Member {
public:
Member(std::string name, ChatRoom& room) : name_(std::move(name)), room_(room) {}
virtual ~Member() = default;
const std::string& name() const { return name_; }
void say(const std::string& text); // defined once ChatRoom is known
virtual void receive(const std::string& from, const std::string& text) {
std::cout << "[" << name_ << "] " << from << ": " << text << "\n";
}
protected:
std::string name_;
ChatRoom& room_; // the ONLY thing a member knows about
};
class ChatRoom {
public:
Member* join(std::unique_ptr<Member> m) {
members_.push_back(std::move(m));
return members_.back().get();
}
void broadcast(const Member& sender, const std::string& text) {
for (const auto& m : members_)
if (m.get() != &sender) m->receive(sender.name(), text);
}
private:
std::vector<std::unique_ptr<Member>> members_;
};
void Member::say(const std::string& text) { room_.broadcast(*this, text); }
class Bot : public Member {
public:
using Member::Member;
void receive(const std::string& from, const std::string&) override {
std::cout << "[" << name_ << "] logged a message from " << from << "\n";
}
};
int main() {
ChatRoom room;
Member* alice = room.join(std::make_unique<Member>("Alice", room));
Member* bob = room.join(std::make_unique<Member>("Bob", room));
room.join(std::make_unique<Bot>("LogBot", room));
alice->say("Standup in 5?");
bob->say("On my way.");
}[Bob] Alice: Standup in 5?
[LogBot] logged a message from Alice
[Alice] Bob: On my way.
[LogBot] logged a message from BobAlice reached Bob and a logging bot she has never heard of — adding LogBot required no change to any existing member.
When should you use it?
GoF's applicability, in plain speech. Use Mediator when:
- A set of objects communicate in well-defined but complex ways and the resulting web of dependencies is unstructured and hard to follow.
- An object is hard to reuse because it refers to and talks with many others — you can't lift it out without bringing the crowd along.
- Behaviour spread across several classes needs to be customisable without a pile of subclasses. Different dialogs display the same widgets but wire them together differently; subclassing every widget per dialog is the tedious alternative this pattern replaces.
Pros and cons
Pros (GoF's consequences)
- It limits subclassing. Because the interaction logic lives in one place, changing behaviour means subclassing the mediator only — colleague classes are reused as they are.
- It decouples colleagues. Colleagues and mediators can be varied and reused independently.
- It simplifies object protocols. Many-to-many interactions become one-to-many between the mediator and its colleagues, which is easier to understand, maintain, and extend.
- It abstracts how objects cooperate. Making mediation its own concept lets you look at how objects interact separately from what each one does individually.
Cons
- It centralises control — GoF's own liability. You've traded complexity of interaction for complexity in the mediator, and because it encapsulates every protocol it can end up more complicated than any colleague and become a monolith that's hard to maintain. GoF observes that a mediator's complexity grows in proportion to the dialog it coordinates.
- One extra hop on every interaction, which adds indirection when you're debugging — and since all traffic goes through one object, a busy mediator can become a bottleneck.
- Moving logic into the mediator can leave components anaemic and hard to understand alone.
Note: Watch the mediator's size. When it grows past a few hundred lines or starts holding business state of its own, split it — one mediator per dialog or per subsystem beats one that coordinates the entire app.
How to remember it
Memory hook: "The control tower." Everyone radios the tower; nobody radios another plane. Contrast with Observer: hub-and-spokes vs broadcast. A mediator knows its colleagues and routes each message to the specific ones that need it; a subject just announces a change to whoever registered and has no opinion about who they are.
Related patterns
- Observer — GoF's stated link, and it's about implementation: colleagues can communicate with the mediator through Observer, with each colleague as a subject and the mediator as its observer.
- Facade — GoF draws the line clearly: a facade abstracts a subsystem to give a more convenient interface, and its protocol is one-way (the facade calls into the subsystem, never the reverse). A mediator's protocol is multidirectional, and it adds cooperative behaviour the colleagues don't have on their own.
- Command — components often notify the mediator by handing it a command object rather than calling a named method.