Skip to content

Mediator Pattern: Talk to the Hub, Not to Each Other

Objects stop calling each other directly and go through one coordinator, so n objects need n links instead of n x (n - 1).

Builds on Coupling and Cohesion and Observer Pattern: Event-Driven Communication

Stage 3advanced3 min read

We add a tower. Flights report to it and wait for instructions. Only the tower knows who is on the runway and who is waiting.

The mediator

interface ControlTower {
  void requestLanding(Flight flight);
  void runwayCleared(Flight flight);
}

A flight can report two events. That is the whole contract (the list of methods every tower must have).

The concrete mediator

class BengaluruTower implements ControlTower {

  private Flight onRunway;
  private final Queue<Flight> holding = new ArrayDeque<>();

  public void requestLanding(Flight flight) {
    if (onRunway == null) {
      onRunway = flight;
      flight.receive("cleared to land on runway 09L");
    } else {
      holding.add(flight);
      flight.receive("hold, you are number " + holding.size() + " in the queue");
    }
  }

  public void runwayCleared(Flight flight) {
    if (flight != onRunway) {
      return;
    }
    onRunway = holding.poll();
    if (onRunway != null) {
      onRunway.receive("runway free, cleared to land on runway 09L");
    }
  }
}

The runway rule and the queue live here and nowhere else. If the runway is free, the flight lands. If not, it joins the queue (holding). When the runway clears, holding.poll() takes the flight that has waited longest, and the tower tells it to land. First come, first served. Nobody polls.

The check flight != onRunway ignores a clear message from a flight that is not on the runway.

The colleague

class Flight {
  private final String code;
  private final ControlTower tower;

  void requestLanding() {
    tower.requestLanding(this);
  }

  void vacateRunway() {
    tower.runwayCleared(this);
  }

  void receive(String instruction) { ... }
}

No list of other flights. No loops. One field points at the tower, and its type is the interface, not BengaluruTower.

Using it

ControlTower tower = new BengaluruTower();
Flight indigo = new Flight("6E-512", tower);
Flight airIndia = new Flight("AI-803", tower);
Flight akasa = new Flight("QP-1104", tower);

There is no addPeer loop. A SpiceJet flight that shows up later is just new Flight("SG-160", tower).

Before vs after

BeforeAfter
Flight-held references for 10 flights90 (10 x 9)10, one per flight, to the tower
Where the runway rule livesInside every FlightBengaluruTower only
Who decides who lands nextNobody, flights keep retryingThe tower, from a FIFO queue
Adding a flightWire it to all existing flightsPass the tower to its constructor
Second runwayChange FlightChange or replace the tower
Testing a flightNeeds real peer flightsNeeds a fake ControlTower

What the run prints

6E-512: requesting landing
   Tower to 6E-512: cleared to land on runway 09L
AI-803: requesting landing
   Tower to AI-803: hold, you are number 1 in the queue
QP-1104: requesting landing
   Tower to QP-1104: hold, you are number 2 in the queue
6E-512: vacated the runway
   Tower to AI-803: runway free, cleared to land on runway 09L
AI-803: vacated the runway
   Tower to QP-1104: runway free, cleared to land on runway 09L
SG-160: requesting landing
   Tower to SG-160: hold, you are number 1 in the queue
QP-1104: vacated the runway
   Tower to SG-160: runway free, cleared to land on runway 09L

AI-803 lands because the tower told it to, not because Main asked a second time.

Things to keep in mind

  • The tower coordinates. It does not fly. Fuel checks, landing gear and so on stay in Flight. If the tower starts doing that, it is turning into a God class (one class that does everything).
  • This tower is not thread safe, so two requests at the same moment could break it. With real concurrent requests you would make requestLanding and runwayCleared synchronized, or run the tower on a single thread. At least that fix is now in one class.
  • If the airport gets more complex (two runways, priority for low fuel), you change the tower and the flights do not notice.

Reference counting and edge cases

“10 references instead of 90” counts only the references stored by flights: one tower reference each, instead of nine peer references. The tower also keeps references to the current and queued flights, so it can call them back. Mediator removes the links between peers, not every reference.

This small sample assumes each flight requests once and only the current flight clears the runway. A repeated request could add the same Flight to the queue twice. A runwayCleared call from the wrong flight is silently ignored. Production code should model explicit flight states, reject duplicate queue entries, log invalid transitions, and make state changes atomic (all or nothing).

Run this version

From the repository root:

mkdir -p out
javac -d out src/DesignPatterns/BehaviouralPatterns/MediatorPattern/After/Main.java
java -cp out DesignPatterns.BehaviouralPatterns.MediatorPattern.After.Main

Read the output as a handoff. A flight reports an event, the tower updates the shared state, and the tower calls the next flight. If BengaluruTower starts calculating fuel, weather, fares, and passenger rules too, it is no longer a coordinator. It has become a God object. Move those decisions into small, separate services or policies, and let the mediator only call them in order.

In this topic

After code1 file

After/Main.java98 linesGitHub
package DesignPatterns.BehaviouralPatterns.MediatorPattern.After;import java.util.ArrayDeque;import java.util.Queue;/** * THE MEDIATOR: the only thing a flight talks to. */interface ControlTower {  void requestLanding(Flight flight);  void runwayCleared(Flight flight);}/** * CONCRETE MEDIATOR: owns the runway rule and the waiting queue. */class BengaluruTower implements ControlTower {  private Flight onRunway;  private final Queue<Flight> holding = new ArrayDeque<>();  @Override  public void requestLanding(Flight flight) {    if (onRunway == null) {      onRunway = flight;      flight.receive("cleared to land on runway 09L");    } else {      holding.add(flight);      flight.receive("hold, you are number " + holding.size() + " in the queue");    }  }  @Override  public void runwayCleared(Flight flight) {    if (flight != onRunway) {      return;    }    onRunway = holding.poll();    if (onRunway != null) {      onRunway.receive("runway free, cleared to land on runway 09L");    }  }}/** * COLLEAGUE: knows the tower, knows nothing about other flights. */class Flight {  private final String code;  private final ControlTower tower;  Flight(String code, ControlTower tower) {    this.code = code;    this.tower = tower;  }  void requestLanding() {    System.out.println(code + ": requesting landing");    tower.requestLanding(this);  }  void vacateRunway() {    System.out.println(code + ": vacated the runway");    tower.runwayCleared(this);  }  void receive(String instruction) {    System.out.println("   Tower to " + code + ": " + instruction);  }}public class Main {  public static void main(String[] args) {    ControlTower tower = new BengaluruTower();    // Each flight gets one reference: the tower. No peer wiring.    Flight indigo = new Flight("6E-512", tower);    Flight airIndia = new Flight("AI-803", tower);    Flight akasa = new Flight("QP-1104", tower);    indigo.requestLanding();    airIndia.requestLanding();    akasa.requestLanding();    // The tower hands the runway to whoever was waiting first.    indigo.vacateRunway();    airIndia.vacateRunway();    // A 4th flight joins with zero changes to the other three.    Flight spiceJet = new Flight("SG-160", tower);    spiceJet.requestLanding();    akasa.vacateRunway();  }}

Done with this topic?

How well do you know it?

PreviousMemento Pattern — Short Notes
Practise 7 questions