Add code examples, and documentation
This commit is contained in:
284
docs/mediator.md
Normal file
284
docs/mediator.md
Normal file
@@ -0,0 +1,284 @@
|
||||
[← 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 {
|
||||
<<interface>>
|
||||
+Notify(sender, request)
|
||||
}
|
||||
class IColleague {
|
||||
<<interface>>
|
||||
+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)
|
||||
Reference in New Issue
Block a user