Multi-Tier Elevator System: Low-Level Design (LLD) with Java Code and Design Patterns
In this answer, we will design a Multi-Tier Elevator System for a building with multiple floors and several elevators. The design will include various OOD patterns such as Strategy Pattern for elevator movement, Command Pattern for processing requests, and Factory Pattern for creating elevator instances. Additionally, we'll discuss algorithms like FIFO (First-In, First-Out), SCAN, and Priority Weight for elevator request scheduling.
Design Overview
In a Multi-Tier Elevator System, we need to manage elevators that move between floors based on requests from passengers. The system should be capable of managing multiple elevators and handling requests efficiently while ensuring that requests are processed in an optimized way.
Key components of the system:
Elevator: Represents an elevator that moves between floors.
Floor: Represents a floor in the building.
Request: Represents a request made by a passenger to either go up or down to a specific floor.
ElevatorControlSystem: Manages multiple elevators and processes requests.
Scheduler: Schedules the request processing using different algorithms.
MovementStrategy: Provides different movement strategies for elevators.
Command: Represents an action to be performed on the elevator system (e.g., request handling).
We will use the following design patterns:
Strategy Pattern for elevator movement algorithms.
Command Pattern for processing requests.
Factory Pattern for creating different types of elevator instances.
Observer Pattern for notifying passengers when their requested elevator arrives.
Classes and Data Structures
1. Elevator Class
The Elevator class represents the elevator that moves between floors.
class Elevator {
private int currentFloor;
private boolean isMoving;
private Direction direction;
private final int id;
public enum Direction {
UP, DOWN, NONE
}
public Elevator(int id) {
this.currentFloor = 0; // Ground floor
this.isMoving = false;
this.direction = Direction.NONE;
this.id = id;
}
public int getCurrentFloor() {
return currentFloor;
}
public void moveTo(int floor) {
this.isMoving = true;
if (currentFloor < floor) {
direction = Direction.UP;
} else {
direction = Direction.DOWN;
}
// Simulate movement by changing the floor number.
this.currentFloor = floor;
this.isMoving = false;
System.out.println("Elevator " + id + " arrived at floor " + floor);
}
public Direction getDirection() {
return direction;
}
public boolean isMoving() {
return isMoving;
}
}
2. Request Class
The Request class represents a request to move an elevator to a specific floor.
class Request {
private int targetFloor;
private int requestTime; // Time when the request was made (used for FIFO algorithm).
public Request(int targetFloor, int requestTime) {
this.targetFloor = targetFloor;
this.requestTime = requestTime;
}
public int getTargetFloor() {
return targetFloor;
}
public int getRequestTime() {
return requestTime;
}
}
3. Scheduler Class (Strategy Pattern)
The Scheduler class decides which algorithm to use for processing elevator requests. We will use a Strategy Pattern to allow dynamic selection of different algorithms (FIFO, SCAN, Priority-based).
interface RequestSchedulingStrategy {
void scheduleRequest(List<Request> requests, List<Elevator> elevators);
}
class FIFOScheduler implements RequestSchedulingStrategy {
@Override
public void scheduleRequest(List<Request> requests, List<Elevator> elevators) {
requests.sort(Comparator.comparingInt(Request::getRequestTime)); // FIFO by request time
processRequests(requests, elevators);
}
private void processRequests(List<Request> requests, List<Elevator> elevators) {
for (Request request : requests) {
Elevator selectedElevator = selectElevatorForRequest(request, elevators);
selectedElevator.moveTo(request.getTargetFloor());
}
}
private Elevator selectElevatorForRequest(Request request, List<Elevator> elevators) {
// Simple heuristic: Choose the closest elevator
return elevators.stream().min(Comparator.comparingInt(elevator -> Math.abs(elevator.getCurrentFloor() - request.getTargetFloor()))).get();
}
}
class SCANScheduler implements RequestSchedulingStrategy {
@Override
public void scheduleRequest(List<Request> requests, List<Elevator> elevators) {
// Sort requests by floor, and process in SCAN order
requests.sort(Comparator.comparingInt(Request::getTargetFloor));
processRequests(requests, elevators);
}
private void processRequests(List<Request> requests, List<Elevator> elevators) {
for (Request request : requests) {
Elevator selectedElevator = selectElevatorForRequest(request, elevators);
selectedElevator.moveTo(request.getTargetFloor());
}
}
private Elevator selectElevatorForRequest(Request request, List<Elevator> elevators) {
// Simple heuristic: Choose the elevator closest to the requested floor
return elevators.stream().min(Comparator.comparingInt(elevator -> Math.abs(elevator.getCurrentFloor() - request.getTargetFloor()))).get();
}
}
class PriorityScheduler implements RequestSchedulingStrategy {
@Override
public void scheduleRequest(List<Request> requests, List<Elevator> elevators) {
// Sort requests by priority (higher priority for lower floors)
requests.sort(Comparator.comparingInt(Request::getTargetFloor));
processRequests(requests, elevators);
}
private void processRequests(List<Request> requests, List<Elevator> elevators) {
for (Request request : requests) {
Elevator selectedElevator = selectElevatorForRequest(request, elevators);
selectedElevator.moveTo(request.getTargetFloor());
}
}
private Elevator selectElevatorForRequest(Request request, List<Elevator> elevators) {
// Simple heuristic: Choose the closest elevator
return elevators.stream().min(Comparator.comparingInt(elevator -> Math.abs(elevator.getCurrentFloor() - request.getTargetFloor()))).get();
}
}
class ElevatorControlSystem {
private List<Elevator> elevators;
private List<Request> requestQueue;
private RequestSchedulingStrategy schedulingStrategy;
public ElevatorControlSystem(int numElevators, RequestSchedulingStrategy schedulingStrategy) {
this.elevators = new ArrayList<>();
for (int i = 0; i < numElevators; i++) {
elevators.add(new Elevator(i + 1));
}
this.requestQueue = new ArrayList<>();
this.schedulingStrategy = schedulingStrategy;
}
public void addRequest(Request request) {
requestQueue.add(request);
}
public void processRequests() {
schedulingStrategy.scheduleRequest(requestQueue, elevators);
}
}
4. Command Pattern for Request Processing
The Command Pattern is used for encapsulating requests as command objects. This allows for flexibility and decouples the request generation from the execution of the request.
import java.util.*;
public class Main {
public static void main(String[] args) {
// Create a system with 3 elevators, using the FIFO scheduling strategy
ElevatorControlSystem elevatorSystem = new ElevatorControlSystem(3, new FIFOScheduler());
// Simulate requests (with time as part of FIFO)
Request request1 = new Request(5, 1); // Request to go to 5th floor at time 1
Request request2 = new Request(3, 2); // Request to go to 3rd floor at time 2
Request request3 = new Request(7, 3); // Request to go to 7th floor at time 3
// Add requests to the system
Command command1 = new RequestCommand(elevatorSystem, request1);
Command command2 = new RequestCommand(elevatorSystem, request2);
Command command3 = new RequestCommand(elevatorSystem, request3);
command1.execute();
command2.execute();
command3.execute();
// Process requests with FIFO scheduling
elevatorSystem.processRequests();
}
}
Explanation of Design Patterns Used
Strategy Pattern:
The RequestSchedulingStrategy interface allows the system to dynamically select different request scheduling algorithms (FIFO, SCAN, Priority Weight). By using this pattern, we can easily switch between different strategies at runtime.
Command Pattern:
The Command interface, along with the RequestCommand class, encapsulates requests as objects, allowing for better flexibility and decoupling the request creation from request processing.
Factory Pattern:
While not explicitly shown here, we could apply the Factory Pattern to instantiate elevators or even scheduling strategies dynamically based on configurations or user inputs.
Observer Pattern:
This pattern can be used for notifying passengers when their requested elevator arrives. For example, each Request object could implement an observer that listens for the completion of the elevator’s movement.
Conclusion
The Low-Level Design (LLD) of the Multi-Tier Elevator System efficiently manages multiple elevators, schedules requests using different strategies, and handles command processing through the Command Pattern. By applying Strategy Pattern, we enable the system to easily switch between different request processing algorithms, such as FIFO, SCAN, and Priority Weight. This design is modular and extensible, making it easy to add new features like additional scheduling strategies or complex elevator behaviors.