| Lesson 13 | Mediator: traffic flow course project |
| Objective | Incorporate the Mediator pattern into the traffic flow system. |
This course project applies the Mediator behavioral design pattern to four directional traffic signals. Instead of controlling one another directly, the North, East, South, and West signals submit green requests through a mediator. The mediator preserves arrival order and decides when the next request may be granted.
This deterministic teaching model is not software for a real roadway controller. It models four independent directions, RED and GREEN aspects, FIFO requests, and a minimum green interval. A production intersection would require conflict groups, clearance and pedestrian phases, sensors, fault detection, overrides, hardware interlocks, safety standards, and extensive verification.
Without a mediator, North might need references to every peer, know which signal is green, track its elapsed time, select the next request, and command each transition. Repeating those decisions across four signals would couple the group, and a new timing rule or signal type could require changes to several classes.
Mediator moves this cross-object policy into one collaboration object. Each TrafficSignal stores its direction and aspect and reports requests through SignalMediator. It never selects or changes a peer. TrafficFlowMediator owns the queue, timing policy, current-green state, and transition order.
The pattern localizes interaction complexity rather than eliminating it. It works best when the mediator remains cohesive around intersection coordination and each signal retains its local state and display behavior. This boundary keeps the collaboration understandable as its rules evolve.
| GoF role | Project participant | Responsibility |
|---|---|---|
| Mediator | SignalMediator | Defines how a signal submits a green request without knowing another signal. |
| ConcreteMediator | TrafficFlowMediator | Registers the four signals, queues requests, tracks elapsed time, and grants the next eligible request. |
| Colleague | TrafficSignal | Stores its direction and aspect, reports requests to the mediator, and displays state changes authorized by the mediator. |
| Colleague state | Direction and Aspect | Represent the identity and current display state used by the collaboration. |
No abstract Colleague superclass is required because TrafficSignal represents every participating signal. Each signal depends only on the narrow mediator interface. The concrete mediator knows the registered signals because it owns their collaboration policy.
The connections represent colleague-to-mediator communication, not direct signal commands. A signal requests green without knowing which peer is active, and the mediator authorizes the required aspect changes.
The minimum interval prohibits an early switch; it does not require rotation when the interval expires. With no waiting request, the current signal may remain green. This demand-driven rule replaces the legacy periodic timer, which changed signals even without demand.
advanceTime provides a deterministic simulated clock, demonstrating ten-second timing without waiting or background threads. A larger application could inject a Clock and scheduler, but scheduling mechanics are separate from Mediator.
The signals initiate meaningful requests, while the mediator determines their effects. No signal inspects or commands a peer.
The following example is a complete single-file Java program. Its nested types keep the course example easy to copy and run. In a larger project, each major type could be moved into its own source file without changing the pattern roles or dependency direction.
import java.time.Duration;
import java.util.ArrayDeque;
import java.util.EnumMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
public class TrafficFlowMediatorDemo {
enum Direction {
NORTH, EAST, SOUTH, WEST
}
enum Aspect {
RED, GREEN
}
interface SignalMediator {
void requestGreen(TrafficSignal signal);
}
static final class TrafficSignal {
private final Direction direction;
private final SignalMediator mediator;
private Aspect aspect = Aspect.RED;
TrafficSignal(Direction direction, SignalMediator mediator) {
this.direction = direction;
this.mediator = mediator;
}
void requestGreen() {
mediator.requestGreen(this);
}
Direction direction() {
return direction;
}
void show(Aspect nextAspect, long atSeconds) {
if (aspect != nextAspect) {
aspect = nextAspect;
System.out.printf("%2ds: %s signal -> %s%n",
atSeconds, direction, aspect);
}
}
}
static final class TrafficFlowMediator implements SignalMediator {
private final long minimumGreenSeconds;
private final Map<Direction, TrafficSignal> signals =
new EnumMap<>(Direction.class);
private final Queue<Direction> requests = new ArrayDeque<>();
private final Set<Direction> queued = new HashSet<>();
private Direction currentGreen;
private long greenSince;
private long simulatedSeconds;
TrafficFlowMediator(Duration minimumGreenTime) {
if (minimumGreenTime.isZero() || minimumGreenTime.isNegative()) {
throw new IllegalArgumentException(
"Minimum green time must be positive");
}
minimumGreenSeconds = minimumGreenTime.toSeconds();
for (Direction direction : Direction.values()) {
signals.put(direction, new TrafficSignal(direction, this));
}
}
TrafficSignal signal(Direction direction) {
return signals.get(direction);
}
@Override
public void requestGreen(TrafficSignal signal) {
Direction requested = signal.direction();
if (requested == currentGreen || !queued.add(requested)) {
return;
}
requests.add(requested);
System.out.printf("%2ds: queued %s request%n",
simulatedSeconds, requested);
grantNextIfAllowed();
}
void advanceTime(Duration elapsed) {
if (elapsed.isNegative()) {
throw new IllegalArgumentException(
"Elapsed time cannot be negative");
}
simulatedSeconds += elapsed.toSeconds();
grantNextIfAllowed();
}
private void grantNextIfAllowed() {
if (requests.isEmpty()) {
return;
}
if (currentGreen != null
&& simulatedSeconds - greenSince
< minimumGreenSeconds) {
return;
}
if (currentGreen != null) {
signals.get(currentGreen).show(Aspect.RED, simulatedSeconds);
}
Direction next = requests.remove();
queued.remove(next);
signals.get(next).show(Aspect.GREEN, simulatedSeconds);
currentGreen = next;
greenSince = simulatedSeconds;
}
}
public static void main(String[] args) {
TrafficFlowMediator mediator =
new TrafficFlowMediator(Duration.ofSeconds(10));
TrafficSignal north = mediator.signal(Direction.NORTH);
TrafficSignal east = mediator.signal(Direction.EAST);
TrafficSignal south = mediator.signal(Direction.SOUTH);
north.requestGreen();
east.requestGreen();
mediator.advanceTime(Duration.ofSeconds(5));
south.requestGreen();
mediator.advanceTime(Duration.ofSeconds(5));
mediator.advanceTime(Duration.ofSeconds(10));
}
}
0s: queued NORTH request
0s: NORTH signal -> GREEN
0s: queued EAST request
5s: queued SOUTH request
10s: NORTH signal -> RED
10s: EAST signal -> GREEN
20s: EAST signal -> RED
20s: SOUTH signal -> GREEN
North is granted immediately, while East and South retain arrival order. Nothing changes at five seconds because North's minimum is incomplete. The ten- and twenty-second transitions show that the mediator owns timing and selection.
TrafficSignal.requestGreen() sends a request through the SignalMediator interface. The signal stores no references to its peers.TrafficFlowMediator.requestGreen() rejects a request from the current green direction, deduplicates queued requests with a Set, appends each new direction to the FIFO Queue, and tries to grant the next request.grantNextIfAllowed() returns without changing the system when the queue is empty or the current minimum green interval is incomplete.RED to the current signal before it sends GREEN to the next signal.EnumMap makes the direction-to-signal registry explicit. The queue preserves request order, while the set prevents duplicate queue positions.TrafficFlowMediator is more than a forwarding object. It applies ordering, duplicate suppression, mutual exclusion, timing eligibility, and transition order. Merely relaying a call from North to East would add indirection without meaningful collaboration policy.
A new queue or timing policy would primarily affect the mediator, while each signal remains focused on requests and authorized displays. Changes to the contract or a signal's fundamental capabilities can still affect both sides. Mediator reduces direct peer coupling; it does not isolate every future change.
| Decision | Benefit | Limitation |
|---|---|---|
| FIFO request queue | Produces predictable arrival-order service | Does not model priorities, emergency preemption, or starvation policy |
| One green direction | Makes mutual exclusion easy to observe | Does not model compatible movement groups |
| Simulated time | Creates deterministic, fast demonstrations and tests | Does not schedule real asynchronous transitions |
| Duplicate suppression | Prevents repeated queue entries for one direction | Does not count repeated demand or traffic volume |
| Centralized coordination | Removes direct signal-to-signal dependencies | The mediator can become too large if unrelated responsibilities accumulate |
The mediator should coordinate this collaboration without absorbing the entire traffic domain. Hardware communication, fault diagnosis, sensors, persistence, interfaces, and policy calculation can remain separate. Owning all of them would turn TrafficFlowMediator into a god object.
State can represent multi-phase intersection modes, Command can represent auditable control requests, and Observer can publish aspect changes. They may complement Mediator, but none replaces its coordination responsibility.
A next version could add conflict groups, yellow and all-red intervals, pedestrian phases, sensor-based fairness, authorized emergency commands, a replaceable clock, event records, fail-safe states, and explicit lifecycle behavior. Tests should cover queue order, duplicates, timing boundaries, invalid durations, red-before-green transitions, and mutual exclusion.
Real-time scheduling requires a threading model. Multiple request threads require the mutable state to be confined to one event loop or protected by a deliberate synchronization policy. Adding synchronized to one method does not prove system-wide thread safety.
A real controller must also handle partial failure. A failed red command must not be followed blindly by a green command. Hardware acknowledgments, independent interlocks, safe fallback states, diagnostics, and regulation are beyond this GoF example. Its successful run validates only the deterministic teaching sequence.
Write or extend a SignalMediator for the course-project intersection. Register all four signals, queue green requests, enforce the minimum green interval, and verify that only the mediator coordinates transitions. A successful Mediator implementation makes interaction policy explicit and testable while keeping each signal independent of its peers.
Signal Mediator Class - Exercise