Elevator System
An elevator's behavior genuinely depends on which of a handful of discrete states it is in - idle, moving, doors open - and the request that just arrived means something different in each one. That makes it a good showcase for the State pattern from the catalog: not because every machine-coding problem needs a pattern, but because this one's requirements already have pattern-shaped seams.
Requirements
Functional
- A hall button (floor + direction) or a cabin button (destination floor) can request the elevator at any time.
- The elevator serves requests in an order that makes sense - it should not reverse direction while requests remain ahead of it in the direction it's already moving.
- Multiple elevators exist; a hall request should go to whichever one can reach that floor soonest, not always elevator #1.
Non-functional
- Adding a new state (say, a maintenance/out-of-service mode) should not require touching the logic of the other states.
- Dispatch (which elevator gets a request) should be swappable independent of how a single elevator processes its own request queue.
Design
Two decisions carry this design. First, Elevator never has an if (state == MOVING)
anywhere in it - it holds a ElevatorState object and forwards every event to it. Second,
picking which elevator answers a hall call is not the elevator's job at all; it belongs to
ElevatorController, so a smarter dispatch algorithm later never touches Elevator.
- 1A passenger on floor 5 wants to go up. The button only knows the controller.
- 2The controller picks the elevator with the smallest floor distance - this is the only class allowed to compare elevators to each other.
- 3The chosen elevator is told to serve floor 5; it does not know it was chosen over others.
- 4Elevator forwards the event to whatever state it currently holds - today that is IdleState.
- 5IdleState decides the elevator should move and swaps in a MovingState.
- 6Each tick, the elevator asks its current state what to do next - move one floor, or stop.
- 7On reaching floor 5, MovingState swaps in DoorOpenState; doors open without Elevator ever checking a floor number itself.
The three states cycle in one direction and only one direction - nothing ever jumps
straight from IdleState to DoorOpenState, because there is no code path that calls it:
Class diagram
Code
Design decisions
- State pattern, not a status flag. An
ElevatorStatusenum withif/switchblocks scattered acrossmove(),requestFloor(), andopenDoors()is where elevator bugs actually live in practice - one branch gets updated for a new state, another is forgotten. Giving each state its own class means the compiler (or at least a code reviewer) can see every place a new state has to plug in. - Dispatch lives in the controller, not the elevator.
Elevatoronly knows how to react to its own requests; it has no idea other elevators exist.ElevatorControlleris the only class that compares elevators to each other, so "nearest elevator" can become "nearest elevator going the same direction" later withoutElevatorchanging at all. - Requests are a sorted structure, not a plain list. Serving requests in floor order (rather than arrival order) is what keeps the elevator from bouncing - the sweep direction only reverses once there is nothing left ahead of it.
- What's missing for a real system: starvation (a request in the opposite direction can
wait through several sweeps under high load - a real answer ages requests or caps sweep
length), and concurrent requests arriving while
move()is mid-step need the request queue to be thread-safe, which this single-threaded walkthrough sidesteps.
Common follow-ups
- How do you stop a request in the opposite direction from starving under constant same-direction demand? Age the request - raise its effective priority the longer it waits - or cap sweep length so the elevator periodically reverses regardless of what is still ahead of it.
- A hall request arrives while
step()is mid-move - what's the risk? The request queue is read and written from different call sites with no synchronization; a real deployment needs it thread-safe (a lock aroundaddRequest, or a concurrent sorted set), which this single-threaded walkthrough intentionally sidesteps. - How would you add a maintenance/out-of-service state? One new class implementing
ElevatorStatethat rejectshandleRequestand reports itself fromstep()-ElevatorandElevatorControllerneed zero changes, since neither branches on a concrete state type. - How does "nearest elevator" dispatch evolve into "nearest elevator already heading the
same direction"? Only
ElevatorController.requestElevatorchanges - it already isolates the elevator-to-elevator comparison, so the new rule is one more term in that comparator.
Check yourself
Why does Elevator never contain an if(state == MOVING) check anywhere in its own code?