Parking Lot: Low-Level Design (LLD) with Multi-Level Support, Layout, and Efficient Management
In this answer, we will design a Multi-Level Parking Lot that handles vehicles efficiently across multiple floors. The goal is to manage parking spaces, track available spots, and ensure efficient utilization of the parking lot. We will incorporate design patterns for modularity, flexibility, and maintainability, while addressing the design levels, class structures, vehicle management, and real-time space availability tracking.
Design Overview
A Multi-Level Parking Lot needs to handle multiple floors, each with different parking spots (compact, standard, and large). To manage this, we will define key components:
ParkingLot: Represents the entire parking lot, managing all floors.
Floor: Represents an individual floor in the parking lot, containing parking spots.
ParkingSpot: Represents an individual parking spot, which may vary in size (compact, standard, large).
Vehicle: Represents the vehicle occupying a parking spot (can be a car, motorcycle, or bus).
VehicleManager: Handles vehicle parking and unparking.
PaymentSystem: Handles the payment based on parking duration (not implemented in the code here but can be added for real-world applications).
ParkingObserver: Observes the parking lot for events like parking and unparking.
Design Levels and Layout
Multi-Level Structure: A multi-level parking lot will have multiple floors, each represented by a Floor class. Each floor contains a set of ParkingSpot objects. Floors can vary in size, for example, some floors may have more compact spots for motorcycles, while others may be designed for larger vehicles like buses.
Layout Consideration: The parking lot should support the following layout:
Compact spots for small vehicles (motorcycles).
Standard spots for regular-sized vehicles (cars).
Large spots for larger vehicles (buses, trucks).
We will ensure that the parking lot layout provides a balance between flexibility and space optimization by grouping the parking spots into different floor levels based on the vehicle types.
Classes and Data Structures
1. ParkingSpot:
A parking spot should be represented with the following attributes:
Size: Determines whether the spot can fit a motorcycle, car, or bus.
Availability: Tracks whether the spot is occupied or free.
Vehicle: A reference to the vehicle currently occupying the spot (if any).
2. Floor:
Each floor will contain:
A list of ParkingSpot objects.
A counter to track the number of available spots, ensuring that space utilization is efficient.
3. ParkingLot:
The parking lot will manage:
A list of Floor objects.
A map to track vehicles by their license plate for easy lookup.
The available spots count across the entire lot.
4. Vehicle:
A vehicle class should contain:
License Plate: Unique identifier for the vehicle.
Size: Whether the vehicle is small, medium, or large.
5. VehicleManager:
A class responsible for managing the parking and unparking of vehicles. It will ensure that:
Vehicles are assigned to parking spots based on their size.
Parking lot space is utilized efficiently by checking available spots across all floors.
Detailed Code Implementation
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
// Step 1: Define Vehicle Types (Strategy Pattern)
abstract class Vehicle {
protected String licensePlate;
protected VehicleSize size;
public Vehicle(String licensePlate, VehicleSize size) {
this.licensePlate = licensePlate;
this.size = size;
}
public VehicleSize getSize() {
return size;
}
public String getLicensePlate() {
return licensePlate;
}
}
enum VehicleSize {
SMALL, MEDIUM, LARGE;
}
class Car extends Vehicle {
public Car(String licensePlate) {
super(licensePlate, VehicleSize.MEDIUM);
}
}
class Motorcycle extends Vehicle {
public Motorcycle(String licensePlate) {
super(licensePlate, VehicleSize.SMALL);
}
}
class Bus extends Vehicle {
public Bus(String licensePlate) {
super(licensePlate, VehicleSize.LARGE);
}
}
// Step 2: Define Parking Spot (Strategy Pattern)
abstract class ParkingSpot {
protected VehicleSize size;
protected boolean isOccupied;
protected Vehicle vehicle;
public ParkingSpot(VehicleSize size) {
this.size = size;
this.isOccupied = false;
}
public boolean parkVehicle(Vehicle vehicle) {
if (!isOccupied && vehicle.getSize() == size) {
this.vehicle = vehicle;
isOccupied = true;
return true;
}
return false;
}
public void freeSpot() {
isOccupied = false;
vehicle = null;
}
public boolean isOccupied() {
return isOccupied;
}
public Vehicle getVehicle() {
return vehicle;
}
}
class CompactSpot extends ParkingSpot {
public CompactSpot() {
super(VehicleSize.SMALL);
}
}
class StandardSpot extends ParkingSpot {
public StandardSpot() {
super(VehicleSize.MEDIUM);
}
}
class LargeSpot extends ParkingSpot {
public LargeSpot() {
super(VehicleSize.LARGE);
}
}
// Step 3: Define Floor (Singleton Pattern)
class Floor {
private List<ParkingSpot> spots;
private AtomicInteger availableSpots;
public Floor(int numSmall, int numMedium, int numLarge) {
spots = new ArrayList<>();
availableSpots = new AtomicInteger(numSmall + numMedium + numLarge);
// Create parking spots for each type
for (int i = 0; i < numSmall; i++) spots.add(new CompactSpot());
for (int i = 0; i < numMedium; i++) spots.add(new StandardSpot());
for (int i = 0; i < numLarge; i++) spots.add(new LargeSpot());
}
public boolean parkVehicle(Vehicle vehicle) {
for (ParkingSpot spot : spots) {
if (spot.parkVehicle(vehicle)) {
availableSpots.decrementAndGet();
return true;
}
}
return false;
}
public void freeSpot(ParkingSpot spot) {
spot.freeSpot();
availableSpots.incrementAndGet();
}
public int getAvailableSpots() {
return availableSpots.get();
}
}
// Step 4: Define ParkingLot (Singleton Pattern)
class ParkingLot {
private static ParkingLot instance;
private List<Floor> floors;
private Map<String, Vehicle> parkedVehicles;
private ParkingLot() {
floors = new ArrayList<>();
parkedVehicles = new HashMap<>();
}
public static synchronized ParkingLot getInstance() {
if (instance == null) {
instance = new ParkingLot();
}
return instance;
}
public void addFloor(Floor floor) {
floors.add(floor);
}
public boolean parkVehicle(Vehicle vehicle) {
for (Floor floor : floors) {
if (floor.parkVehicle(vehicle)) {
parkedVehicles.put(vehicle.getLicensePlate(), vehicle);
return true;
}
}
return false;
}
public boolean freeVehicle(String licensePlate) {
Vehicle vehicle = parkedVehicles.get(licensePlate);
if (vehicle != null) {
for (Floor floor : floors) {
for (ParkingSpot spot : floor.spots) {
if (spot.getVehicle() == vehicle) {
floor.freeSpot(spot);
parkedVehicles.remove(licensePlate);
return true;
}
}
}
}
return false;
}
public int getAvailableSpots() {
int available = 0;
for (Floor floor : floors) {
available += floor.getAvailableSpots();
}
return available;
}
}
// Step 5: Vehicle Manager for Parking/Unparking Vehicles
class VehicleManager {
private ParkingLot parkingLot;
public VehicleManager(ParkingLot parkingLot) {
this.parkingLot = parkingLot;
}
public boolean parkVehicle(Vehicle vehicle) {
return parkingLot.parkVehicle(vehicle);
}
public boolean unparkVehicle(String licensePlate) {
return parkingLot.freeVehicle(licensePlate);
}
public int getAvailableSpots() {
return parkingLot.getAvailableSpots();
}
}
// Step 6: Observer Pattern for Logging and Monitoring
interface ParkingObserver {
void update(String message);
}
class ParkingLogger implements ParkingObserver {
@Override
public void update(String message) {
System.out.println("Parking Event: " + message);
}
}
// Step 7: Factory Method for Vehicle Creation
class VehicleFactory {
public static Vehicle createVehicle(String type, String licensePlate) {
switch (type) {
case "Car":
return new Car(licensePlate);
case "Motorcycle":
return new Motorcycle(licensePlate);
case "Bus":
return new Bus(licensePlate);
default:
throw new IllegalArgumentException("Unknown vehicle type");
}
}
}
// Step 8: Main Class for Demonstration
public class Main {
public static void main(String[] args) {
// Create a parking lot instance (Singleton)
ParkingLot parkingLot = ParkingLot.getInstance();
// Add some floors to the parking lot
Floor floor1 = new Floor(10, 10, 5); // 10 small, 10 medium, 5 large
parkingLot.addFloor(floor1);
// Add observers (logging)
ParkingLogger logger = new ParkingLogger();
// Create vehicle manager for parking/unparking vehicles
VehicleManager manager = new VehicleManager(parkingLot);
// Park some vehicles
Vehicle car1 = VehicleFactory.createVehicle("Car", "ABC123");
Vehicle motorcycle1 = VehicleFactory.createVehicle("Motorcycle", "XYZ456");
System.out.println("Available spots before parking: " + parkingLot.getAvailableSpots());
manager.parkVehicle(car1);
manager.parkVehicle(motorcycle1);
System.out.println("Available spots after parking: " + parkingLot.getAvailableSpots());
// Free a vehicle
manager.unparkVehicle("ABC123");
System.out.println("Available spots after freeing car1: " + parkingLot.getAvailableSpots());
}
}
Explanation of Design
1. Multi-Level Structure:
ParkingLot contains multiple Floor objects, each representing a different floor in the multi-level parking lot.
The Floor class has different parking spots (compact, standard, large), and space availability is tracked dynamically.
2. Efficient Space Utilization:
The ParkingManager ensures that the correct size vehicle is parked in a suitable spot. It dynamically checks all floors to find the most suitable parking spot.
The Floor class maintains an AtomicInteger counter to track the available spots, updating in real-time when spots are taken or freed.
3. Real-Time Tracking of Available Parking Space:
The ParkingLot provides an easy way to get the total number of available spots across all floors. Each Floor keeps track of its own available spots, and when