An elevator system is a combination of mechanical components and software logic used in multi-story buildings to transport people or goods vertically between floors.
Modern elevator systems typically consist of one or more elevator cars (also called lifts), each controlled by an embedded software system that manages a range of operations, including:
In this chapter, we will explore the low-level design of an elevator system in detail.
Lets start by clarifying the requirements:
Before starting the design, it's important to ask thoughtful questions to uncover hidden assumptions and better define the scope of the system.
Here is an example of how a conversation between the candidate and the interviewer might unfold:
Candidate: Should we assume a single elevator or support multiple elevators operating in the same building?
Interviewer: Let’s design for a system with multiple elevators in the same building.
Candidate: Should the system handle both internal requests (floor buttons inside the elevator) and external requests (hall calls from each floor)?
Interviewer: Yes, the system should support both internal and external requests.
Candidate: Should we follow a specific elevator scheduling algorithm, like SCAN or LOOK, or is a basic first-come-first-serve strategy sufficient?
Interviewer: For this version, use a simple scheduling strategy like nearest elevator first. However, the design should be extensible enough to support pluggable scheduling algorithms in the future.
Candidate: Should the elevators display their current floor and movement direction (up/down)?
Interviewer: Yes, each elevator should display its current floor and movement direction in real-time.
After gathering the details, we can summarize the key system requirements.
After the requirements are clear, lets identify the core entities/objects we will have in our system.
Core entities are the fundamental building blocks of our system. We identify them by analyzing key nouns (e.g., elevator, request, floor, direction, display) and actions (e.g., move, assign, display, handle) from the functional requirements. These typically translate into classes, enums, or interfaces in an object-oriented design.
Below, we break down the functional requirements and extract the relevant entities. Related requirements are grouped together when they correspond to the same conceptual entities.
This clearly suggests the need for an Elevator
entity that encapsulates the state and behavior of an individual elevator.
We also need an ElevatorSystem
(or ElevatorController
) entity to manage all elevators, process incoming requests, and delegate tasks to the appropriate elevator.
This implies a Request
entity that captures floor requests, their origin (inside or outside the elevator), and direction.
To represent direction, we define a Direction
enum with values UP
and DOWN
.
This leads to the need for an ElevatorDisplay
entity. Each elevator is equipped with a display component that continuously updates and shows the elevator’s current floor and movement direction.
ElevatorSystem
: Manages all elevators in the building and routes external requests to the best-suited elevator.Elevator
: Represents a single elevator with attributes like current floor, direction, request queue, and operational state.Request
: Encapsulates a user’s request to go to or from a floor, with associated direction and type.Direction
(Enum): Represents the intended direction of travel—UP or DOWN.ElevatorDisplay
: Shows current floor and movement direction of an elevator in real time.Direction
: A simple enumeration (UP
, DOWN
, IDLE
) that defines the possible movement states of an elevator. This is crucial for both request handling and state management.RequestSource
: Distinguishes between requests made from inside the elevator cabin (INTERNAL
) and those from a building floor (EXTERNAL
). This helps in prioritizing and routing requests correctly.Request
A data-centric class that encapsulates all information about a user's request.
It holds the targetFloor
, the desired direction
(for external requests), and the source
. This makes passing request information throughout the system clean and simple.
Elevator
This is the central active object representing a single elevator car.
It implements Runnable
to operate on its own thread, manages its internal request queues (upRequests
, downRequests
), and maintains its current state (IdleState
, MovingUpState
, etc.). It also acts as the "Subject" in the Observer pattern.
ElevatorSystem
The main controller and entry point for the entire system.
It's a Singleton that initializes and manages all Elevator
instances. It acts as a Facade, providing a simple API for external and internal requests while hiding the underlying complexity of elevator selection and request dispatching.
The relationships between classes are designed to promote low coupling and high cohesion.
A "has-a" relationship where the part cannot exist without the whole
ElevatorSystem
has an ElevatorSelectionStrategy
: The strategy is an essential, integral part of the system, created and owned by it.ElevatorSystem
has Elevator
s: The system creates, manages, and contains the elevators. The elevators' lifecycle is directly controlled by the ElevatorSystem
and its thread pool.Elevator
has an ElevatorState
: Every elevator has a state object that defines its current behavior. The elevator manages the lifecycle of its state objects.A "has-a" relationship where the part can exist independently
Elevator
has ElevatorObserver
s: The Elevator
holds a list of observers (like Display
), but the observers are created externally and can exist independently of the elevator.IdleState
, MovingUpState
, and MovingDownState
implement the ElevatorState
interface.NearestElevatorStrategy
implements the ElevatorSelectionStrategy
interface.Display
implements the ElevatorObserver
interface.Elevator
implements the Runnable
interface, making it an active object that can run on a thread.Elevator
processes Request
s: An elevator maintains collections of requests it needs to serve.ElevatorSystem
receives and delegates Request
s to the appropriate elevator.Several design patterns are employed to create a robust and flexible system architecture.
The ElevatorSystem
class is implemented as a Singleton to ensure there is only one instance controlling the entire building's elevator network. This provides a single, global point of access and prevents conflicting states.
This pattern is used to select an elevator for an external request.
ElevatorSystem
ElevatorSelectionStrategy
NearestElevatorStrategy
ElevatorSystem
.This pattern manages the complex, state-dependent behavior of an elevator.
Elevator
ElevatorState
IdleState
, MovingUpState
, MovingDownState
Elevator
class becomes simpler, as it just delegates actions to the current state object. This makes the code easier to understand and extend (e.g., adding a MaintenanceState
).This pattern provides a way to notify multiple objects about state changes in an elevator.
Elevator
ElevatorObserver
(implemented by Display
)Elevator
from its observers. We can attach any number of displays, loggers, or monitoring tools to an elevator without changing its code. The elevator simply notifies all registered observers when its floor or direction changes.The ElevatorSystem
acts as a facade. It provides a simple, unified interface (requestElevator()
, selectFloor()
) to the more complex underlying subsystem of elevators, states, strategies, and threads. This simplifies the interaction for the client (ElevatorSystemDemo
).
Direction
and RequestSource
Direction
defines the elevator’s movement state.RequestSource
distinguishes between cabin (internal) and hall (external) requests—critical for prioritizing and routing.Encapsulates all the information needed to process a user's request.
To provide real-time updates on the elevator's status without coupling the Elevator class to a specific display mechanism, we use the Observer pattern.
The Elevator is the "Subject" and Display is the "Observer". The Elevator notifies all its registered observers (notifyObservers()) whenever its state (floor, direction) changes. This allows us to add any number of different observers (e.g., a logging service, a graphical UI, a maintenance monitor) without modifying the Elevator class.
To make the algorithm for assigning an external request to an elevator pluggable, we use the Strategy pattern.
ElevatorSelectionStrategy interface defines a contract for any selection algorithm. By programming to this interface, the main ElevatorSystem is decoupled from the specific implementation of the selection logic. We could easily introduce new strategies (e.g., "least busy elevator," "energy-saving elevator") without changing the core system.
NearestElevatorStrategy selects the best elevator for a hall request using the nearest moving-in-same-direction or idle heuristic.
The behavior of an elevator (how it moves, how it accepts new requests) changes drastically depending on whether it's idle, moving up, or moving down. The State pattern is a perfect fit to manage this complexity.
ElevatorState
This interface defines the operations that depend on the elevator's state. The Elevator class will delegate calls to these methods to its current state object, effectively changing its behavior by changing its state object.
Each state class encapsulates the logic for that specific state. For example, MovingUpState only concerns itself with servicing the next highest floor in its queue.
The states are responsible for managing transitions. For instance, when MovingUpState has no more up-requests, it transitions the elevator's state to IdleState (or MovingDownState if down-requests exist). This keeps the transition logic clean and localized.
The Elevator class brings together the State and Observer patterns. It runs in its own thread to simulate independent operation.
Explanation:
This class acts as the central coordinator and public-facing API (Facade) for the entire system.
The class is a Singleton to ensure there's only one control system. It provides a simple, clean API (requestElevator, selectFloor) that hides the internal complexity of strategies, states, and threads.
It uses a FixedThreadPool to manage the Elevator threads. This provides a bounded resource pool for the active elevator objects.
The main method demonstrates the end-to-end flow of the system, simulating user interactions and showing how the different components work together.