Design Patterns «Prev Next»

Lesson 8Course project, part 2
ObjectiveWrite classes for traffic signals

Course Project - Traffic Signals

Requirements (distilled)

  1. Car traffic light: cycles RED → GREEN → YELLOW → RED in order; minimum time per state; typically no maximum on GREEN.
  2. Pedestrian signal: cycles WALK → FLASHING_DONT_WALK → DONT_WALK → WALK with a minimum time per state.
  3. Push-to-walk: pedestrians press a button to request a crossing; without requests, the ped signal remains in DONT_WALK while cars stay GREEN.
  4. Coupling/invariant: whenever the car light is GREEN, the ped signal must be DONT_WALK (never WALK/FLASHING).

Design

State Models

Java Implementation (concise, extendable)

Enums and timing

import java.time.Instant;
import java.util.concurrent.*;

enum CarState { RED, GREEN, YELLOW }
enum PedState { DONT_WALK, WALK, FLASHING_DONT_WALK }

final class Timing {
  // Seconds (tune for your simulator / tests)
  static final int CAR_MIN_GREEN  = 20;
  static final int CAR_YELLOW     = 4;
  static final int PED_WALK_MIN   = 7;
  static final int PED_FLASH_MIN  = 5;
}

Car traffic light

final class CarLight {
  private CarState state = CarState.GREEN;
  private Instant  since = Instant.now();

  CarState state() { return state; }
  long ageSeconds() { return java.time.Duration.between(since, Instant.now()).getSeconds(); }

  void toRed()    { state = CarState.RED;    since = Instant.now(); }
  void toGreen()  { state = CarState.GREEN;  since = Instant.now(); }
  void toYellow() { state = CarState.YELLOW; since = Instant.now(); }

  boolean minGreenSatisfied() { return state == CarState.GREEN && ageSeconds() >= Timing.CAR_MIN_GREEN; }
}

Pedestrian signal

final class PedSignal {
  private PedState state = PedState.DONT_WALK;
  private Instant  since = Instant.now();
  private volatile boolean pendingRequest = false;

  PedState state() { return state; }
  long ageSeconds() { return java.time.Duration.between(since, Instant.now()).getSeconds(); }

  void pressButton() { pendingRequest = true; }
  boolean hasRequest() { return pendingRequest; }
  void clearRequest() { pendingRequest = false; }

  void toDontWalk()          { state = PedState.DONT_WALK;          since = Instant.now(); }
  void toWalk()              { state = PedState.WALK;                since = Instant.now(); }
  void toFlashingDontWalk()  { state = PedState.FLASHING_DONT_WALK;  since = Instant.now(); }

  boolean minWalkSatisfied()   { return state == PedState.WALK &&  ageSeconds() >= Timing.PED_WALK_MIN; }
  boolean minFlashSatisfied()  { return state == PedState.FLASHING_DONT_WALK &&  ageSeconds() >= Timing.PED_FLASH_MIN; }
}

Coordinator - the safety brain

final class IntersectionCoordinator implements AutoCloseable {
  private final ScheduledExecutorService sched = Executors.newSingleThreadScheduledExecutor();
  private final CarLight  car;
  private final PedSignal ped;

  IntersectionCoordinator(CarLight car, PedSignal ped) {
    this.car = car; this.ped = ped;
    validateInvariant();
    scheduleIdleLoop();
  }

  // Public API: simulate a push button
  void requestCrossing() { ped.pressButton(); }

  // Idle: if car is GREEN with no request, stay green; periodically check for requests
  private void scheduleIdleLoop() {
    sched.scheduleAtFixedRate(this::tick, 0, 500, java.util.concurrent.TimeUnit.MILLISECONDS);
  }

  private void tick() {
    try {
      // If a request exists and car green min time is satisfied, start ped phase
      if (ped.hasRequest() &&  car.state() == CarState.GREEN &&  car.minGreenSatisfied()) {
        beginPedestrianPhase();
      }
      // If in the middle of a phase, drive it forward
      advanceIfNeeded();
      validateInvariant();
    } catch (Exception ex) {
      ex.printStackTrace(); // for course project; replace with logging in production
    }
  }

  private void beginPedestrianPhase() {
    ped.clearRequest();

    // Car: GREEN → YELLOW (fixed duration), then → RED
    if (car.state() == CarState.GREEN) car.toYellow();
    sched.schedule(() -> {
      if (car.state() == CarState.YELLOW) car.toRed();
      // Ped: DONT_WALK → WALK
      if (ped.state() == PedState.DONT_WALK) ped.toWalk();
      validateInvariant();
    }, Timing.CAR_YELLOW, TimeUnit.SECONDS);
  }

  private void advanceIfNeeded() {
    // Advance pedestrian cycle
    if (ped.state() == PedState.WALK && ped.minWalkSatisfied()) {
      ped.toFlashingDontWalk();
    } else if (ped.state() == PedState.FLASHING_DONT_WALK && ped.minFlashSatisfied()) {
      ped.toDontWalk();
      // Return car to GREEN when pedestrians have cleared
      if (car.state() == CarState.RED) car.toGreen();
    }
  }

  // Safety: whenever car is GREEN, ped must be DONT_WALK
  private void validateInvariant() {
    if (car.state() == CarState.GREEN && ped.state() != PedState.DONT_WALK) {
      throw new IllegalStateException("Safety invariant violated: GREEN requires DONT_WALK.");
    }
  }

  @Override public void close() { sched.shutdownNow(); }
}

Demo harness

public final class TrafficSimulation {
  public static void main(String[] args) throws Exception {
    CarLight car = new CarLight();
    PedSignal ped = new PedSignal();
    try (IntersectionCoordinator ctl = new IntersectionCoordinator(car, ped)) {
      // Simulate a pedestrian request after some time:
      TimeUnit.SECONDS.sleep(5);
      ctl.requestCrossing();
      // Let the simulation run long enough to cycle:
      TimeUnit.SECONDS.sleep(Timing.CAR_MIN_GREEN + Timing.CAR_YELLOW + Timing.PED_WALK_MIN + Timing.PED_FLASH_MIN + 5);
    }
  }
}

Notes and Extensions

Abstract Traffic Signal - Exercise

Implement parameterized timings and add a second car direction (east–west) with an all-red clearance phase. Abstract Traffic Signal - Exercise

SEMrush Software 8 SEMrush Banner 8