Skip to main content

Traffic Control

Design the controller behind a single road intersection: a set of traffic lights that cycle through colors, driven by either a fixed timer or a sensor that notices nobody is waiting. The prompt looks like a scheduling problem, but the useful decision is where the "what comes next" logic lives.

Requirements

Functional

  • Each TrafficLight at the intersection is always in exactly one of RED, YELLOW, GREEN.
  • The intersection cycles lights so that never more than one direction is GREEN at once.
  • A light can advance on a fixed timer (GREEN for 30s, YELLOW for 5s, ...) or be interrupted by a sensor signaling that its lane is empty and another lane has traffic waiting.
  • An operator can force the intersection into an all-RED state for maintenance without restarting the controller.

Non-functional

  • Adding a new light color, or a whole new transition rule (a pedestrian-crossing phase, say), should not mean rewriting the switch statement inside IntersectionController.
  • Every transition must go through validation - a light can never jump straight from RED to YELLOW, whatever triggered the change.

Design

The color itself becomes an object instead of a field: a LightState interface with one method, next(), that returns the state to transition into. TrafficLight just holds "whichever state object is current" and asks it what comes next - it never contains an if (color == RED) anywhere. This is the same shape as OrderStatus from Objects, classes, interfaces - a closed set of named values - except here each value also carries behavior (its own transition rule), so it earns the extra machinery of the State pattern rather than staying a plain enum.

Timer / SensorIntersectionControllerTrafficLightLightStatetick() / laneEmpty()1advance()2next()3currentState = next4getColor()5
  1. 1A fixed timer or a sensor event is the only thing that ever asks for a change - the controller does not poll.
  2. 2The controller picks which light’s turn it is and tells it to move on. It never picks the color itself.
  3. 3The light asks its current state what comes after it - RED does not know about YELLOW, it just returns it.
  4. 4The light swaps in the new state object; that swap is the entire transition.
  5. 5Anyone downstream (a display, a log) reads the color through this, never through the state object.

IntersectionController coordinates multiple TrafficLights and owns the timing/sensor input; it never touches a light's internal state directly, only calls advance().

Class diagram

«interface»LightState+ next(): LightState+ getColor(): ColorIntersectionController- lights: List<TrafficLight>+ advanceNext()+ forceAllRed()TrafficLight- id: string- currentState: LightState+ advance()+ getColor(): ColorRedState+ next(): LightStateYellowState+ next(): LightStateGreenState+ next(): LightState
implementsuses
TrafficLight delegates 'what's next' to its current LightState; IntersectionController only calls advance().

Code

import java.util.*;
 
enum Color { RED, YELLOW, GREEN }
 
interface LightState {
LightState next();
Color getColor();
}
 
class RedState implements LightState {
public LightState next() { return new GreenState(); }
public Color getColor() { return Color.RED; }
}
 
class YellowState implements LightState {
public LightState next() { return new RedState(); }
public Color getColor() { return Color.YELLOW; }
}
 
class GreenState implements LightState {
public LightState next() { return new YellowState(); }
public Color getColor() { return Color.GREEN; }
}
 
class TrafficLight {
final String id;
private LightState currentState;
 
TrafficLight(String id) {
this.id = id;
this.currentState = new RedState();
}
 
void advance() {
currentState = currentState.next();
}
 
Color getColor() {
return currentState.getColor();
}
}
 
class IntersectionController {
private final List<TrafficLight> lights;
private int turn = 0;
 
IntersectionController(List<TrafficLight> lights) {
this.lights = lights;
}
 
void advanceNext() {
lights.get(turn).advance();
turn = (turn + 1) % lights.size();
}
 
void forceAllRed() {
for (TrafficLight light : lights) {
while (light.getColor() != Color.RED) {
light.advance();
}
}
}
}

Design decisions

  • Colors are LightState objects, not an enum with a switch elsewhere. An enum would work for storage, but the transition rule (RED -> GREEN -> YELLOW -> RED) would have to live somewhere, and that somewhere is always a big conditional that grows every time a phase is added. Giving each color its own next() keeps every transition rule next to the state it belongs to.
  • A sensor interrupt is just an early call to advance(), not a separate code path. TrafficLight doesn't know or care whether advance() was called because a timer fired or because a sensor reported an empty lane - both are "something decided it's time to move on." That's what let the sensor requirement bolt on without touching LightState.
  • IntersectionController never mutates a light's color field. It holds a list of TrafficLights and calls advance() on the one whose turn it is; the light is the only class allowed to reassign its own state. This keeps the "never two greens at once" invariant enforceable in one place - the controller's turn-taking logic - instead of scattered across every light.
  • What's missing for a real system: a maintenance all-red mode needs a state that refuses to advance until explicitly released (a MaintenanceState that overrides next() to return itself), and coordinating a full intersection safely needs a transition guard so a light can't go GREEN until its cross-traffic sibling has confirmed RED - both left out here to keep the example to one clean State-pattern demo rather than a full safety-interlock system.
0%0 of 122 pages studied