[← Design patterns index](README.md) # Mediator **Category:** Behavioral | **Also known as: Intermediary, Controller** | **Source:** `patterns/mediator/AirTrafficControl/` ## Problem An airport tower needs to arbitrate five runways among any number of flights and ground vehicles. Wire that directly, and each `Flight` and `GroundVehicle` needs its own reference to the runway array and to every other aircraft currently occupying a runway. Every colleague then duplicates the same allocation logic. A new colleague type, such as a snowplow, needs the same wiring again. ## Solution Give every colleague exactly one reference: to a **mediator**. Colleagues stop tracking runway state themselves — they send a request (`RequestLanding`, `RequestTakeoff`, `RequestRunwayCrossing`, …) to the mediator and wait to be told `accepted: true/false`. The mediator alone owns the five `Runway` slots and decides who gets a runway. Colleagues never learn about each other at all. ## Participants (GoF names → this repo) - **Mediator** → `IMediator` — declares `Notify(sender, request)`. - **ConcreteMediator** → `AirTrafficControlTower` — owns the five runways and is the only class that knows the allocation rules. - **Colleague** → `IColleague` — declares `ReceiveMessage(instruction, accepted)`. - **ConcreteColleague** → `Flight`, `GroundVehicle` — send requests, receive accept/reject callbacks; neither knows the other exists. ```mermaid classDiagram class IMediator { <> +Notify(sender, request) } class IColleague { <> +ReceiveMessage(instruction, accepted) } class AirTrafficControlTower { -Runway[5] _runways +Notify(sender, request) } class Flight { +RequestLanding() +RequestTakeoff() } class GroundVehicle { +RequestRunwayCrossing() } IMediator <|.. AirTrafficControlTower IColleague <|.. Flight IColleague <|.. GroundVehicle Flight --> IMediator : Notify(this, request) GroundVehicle --> IMediator : Notify(this, request) AirTrafficControlTower --> IColleague : ReceiveMessage(instruction, accepted) ``` Note the arrow direction: `Flight`/`GroundVehicle` only ever point at `IMediator`; `AirTrafficControlTower` only ever points back at `IColleague`. Neither colleague type points at the other. ## Code ```csharp // Program.cs — Mediator + Colleague interfaces public interface IColleague { void ReceiveMessage(string instruction, bool accepted); } public interface IMediator { void Notify(IColleague sender, AirTrafficRequest request); } // Flight — ConcreteColleague: only ever talks to _mediator public class Flight : IColleague { private readonly IMediator _mediator; public string FlightNumber { get; } public Flight(string flightNumber, IMediator mediator) { FlightNumber = flightNumber; _mediator = mediator; } public void RequestLanding() => _mediator.Notify(this, AirTrafficRequest.LandingRequest); public void LandingCompleted() => _mediator.Notify(this, AirTrafficRequest.LandingRequestDone); public void ReceiveMessage(string instruction, bool accepted) => Console.WriteLine(accepted ? $"Flight {FlightNumber} received instruction: {instruction}" : $"Flight {FlightNumber} received rejection for instruction: {instruction}"); // RequestTakeoff() / TakeoffCompleted() follow the same shape } // GroundVehicle mirrors Flight exactly, but for RequestRunwayCrossing() / // RunwayCrossingCompleted() — same interface, same "only knows the mediator" shape // AirTrafficControlTower — ConcreteMediator: the only class that knows // about runways and occupancy public class AirTrafficControlTower : IMediator { private class Runway { public string Name { get; init; } = ""; public IColleague? OccupiedBy { get; set; } public Runway(string name) => Name = name; } private readonly Runway[] _runways = new Runway[5]; public AirTrafficControlTower() { for (int i = 0; i < _runways.Length; i++) _runways[i] = new Runway($"Runway R{i + 1}"); } public void Notify(IColleague sender, AirTrafficRequest request) { switch (request) { case AirTrafficRequest.LandingRequest: HandleLandingRequest(sender); break; case AirTrafficRequest.TakeoffRequest: HandleTakeoffRequest(sender); break; case AirTrafficRequest.RunwayCrossingRequest: HandleRunwayCrossingRequest(sender); break; case AirTrafficRequest.LandingRequestDone: HandleLandingRequestDone(sender); break; case AirTrafficRequest.TakeoffRequestDone: HandleTakeoffRequestDone(sender); break; case AirTrafficRequest.RunwayCrossingRequestDone: HandleRunwayCrossingRequestDone(sender); break; } } void HandleLandingRequest(IColleague sender) { for (int i = 0; i < _runways.Length; i++) { if (_runways[i].OccupiedBy is null) { _runways[i].OccupiedBy = sender; sender.ReceiveMessage($"Landing approved on {_runways[i].Name}.", accepted: true); return; } } sender.ReceiveMessage("No available runways for landing.", accepted: false); } // HandleTakeoffRequest / HandleRunwayCrossingRequest mirror // HandleLandingRequest; the three Handle*Done methods clear whichever // runway `sender` occupies } ``` ```csharp // Program.cs — client: flat construction, then requests through the tower only var tower = new AirTrafficControlTower(); var flight1 = new Flight("AC101", tower); var vehicle1 = new GroundVehicle("V1", tower); flight1.RequestLanding(); // approved: Runway R1 free // ... 4 more requests fill R2–R5 ... vehicle2.RequestRunwayCrossing(); // rejected: no runways left flight1.LandingCompleted(); // frees R1 vehicle2.RequestRunwayCrossing(); // now approved, on the freed R1 ``` ## Output (verified) ``` --- Occupying all 5 runways --- Flight AC101 requesting landing. Flight AC101 received instruction: Landing approved on Runway R1. Flight WJ202 requesting takeoff. Flight WJ202 received instruction: Takeoff approved on Runway R2. Flight DL303 requesting landing. Flight DL303 received instruction: Landing approved on Runway R3. Flight UA404 requesting takeoff. Flight UA404 received instruction: Takeoff approved on Runway R4. Ground vehicle V1 requesting runway crossing. Ground vehicle V1 received instruction: Crossing approved on Runway R5. --- No runways left: this request should be rejected --- Ground vehicle V2 requesting runway crossing. Ground vehicle V2 received rejection for instruction: No available runways for crossing. --- Freeing up a runway: the previously rejected request can now succeed --- Flight AC101 has completed landing. Flight AC101 received instruction: You have cleared Runway R1 after landing. Ground vehicle V2 requesting runway crossing. Ground vehicle V2 received instruction: Crossing approved on Runway R1. --- Clearing the remaining runways --- Flight WJ202 has completed takeoff. Flight WJ202 received instruction: You have cleared Runway R2 after takeoff. Flight DL303 has completed landing. Flight DL303 received instruction: You have cleared Runway R3 after landing. Flight UA404 has completed takeoff. Flight UA404 received instruction: You have cleared Runway R4 after takeoff. Ground vehicle V1 has completed runway crossing. Ground vehicle V1 received instruction: You have cleared Runway R5 after crossing. Ground vehicle V2 has completed runway crossing. Ground vehicle V2 received instruction: You have cleared Runway R1 after crossing. ``` ## GoF pseudocode (dialog coordinating its form controls) ``` interface Mediator is method notify(sender: Component, event: string) class AuthenticationDialog implements Mediator is field loginOrRegisterChkBx: Checkbox field okBtn: Button method notify(sender, event) is if (sender == loginOrRegisterChkBx and event == "check") // show/hide login vs. registration fields, retitle the dialog if (sender == okBtn and event == "click") // validate credentials or create the account class Component is field dialog: Mediator constructor Component(dialog) is this.dialog = dialog method click() is dialog.notify(this, "click") class Checkbox extends Component is method check() is dialog.notify(this, "check") ``` This repo's version tracks the canonical shape closely: `Notify(sender, request)` takes the same two arguments as GoF's `notify(sender, event)` — only an `AirTrafficRequest` enum in place of a bare string, and an `IMediator` interface, so colleagues aren't wired to one concrete mediator class. Where the GoF sketch dispatches on `sender` identity inside a single `notify`, this repo dispatches on the request type and lets five `Handle*` methods each scan `_runways` — same idea, more moving parts because there are more request kinds than the two-widget dialog example needs. ## Applicability - A set of objects communicate in well-defined but complex ways, and the resulting interdependencies are unstructured and hard to follow. - Reusing an object is hard because it refers to and talks to many others. - Behavior distributed across several classes should be customizable without lots of subclassing — write a new mediator instead. ## Pros / Cons | Pros | Cons | | ------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ | | Single Responsibility — collaboration logic sits in one place | The mediator can become a God Object / monolith | | Open/Closed — new mediators without touching components | It centralizes control: complexity moves rather than vanishes | | Components decouple and become individually reusable | Five near-identical `Handle*` methods here already show the mediator absorbing complexity the colleagues no longer carry | | Replaces many-to-many interactions with one-to-many | | ## Seen in the wild (.NET) - WinForms/WPF code-behind acting as dialog director. - The MediatR library. - SignalR hubs routing between clients. - A real air-traffic-control tower — this repo's own example, and the book's analogy: pilots never talk to each other, only to the tower. ## Related patterns - **Chain of Responsibility / Command / Mediator / Observer** are the four ways to connect senders and receivers: CoR passes along a chain until handled; Command makes a one-way sender→receiver link; **Mediator removes direct links, forcing a hub**; Observer lets receivers subscribe dynamically. - **Facade vs. Mediator** — directionality (see Facade). - **Mediator vs. Observer** — genuinely blurry. Distinguishing goal: Mediator eliminates mutual dependencies among a known set of components by routing every interaction through a fixed hub; Observer establishes dynamic one-way subscriptions where any number of unknown subscribers can attach later. --- **← Prev:** [Facade](facade.md) · [All patterns](README.md) · **Next →** [Memento](memento.md)