Back to LLD explorer
Object-Oriented Design• Patterns: Singleton, Factory Method, Strategy, Observer

Parking Lot System

Medium

Problem Summary

Design a multi-story, multi-gate Parking Lot management system supporting diverse vehicle types (Motorcycle, Car, Truck, EV), dynamic spot assignment, and pluggable pricing strategies.

Functional Scope

  • Multiple floors with dedicated spot sizes: Compact, Large, Motorcycle, and EV Charging spots.
  • Dynamic allocation of the nearest available spot on entry.
  • Issue ticket with timestamp, gate id, and assigned spot.
  • Pluggable pricing strategy (Flat rate, Hourly, Weekend surge).
  • Thread-safe spot assignment preventing race conditions across multiple simultaneous entry gates.

Entity-Relationship (ER) Schema

ParkingLot [1] --> [*] ParkingFloor
ParkingFloor [1] --> [*] ParkingSpot
ParkingSpot [1] --> [0..1] Vehicle
Ticket [1] --> [1] ParkingSpot
Ticket [1] --> [1] Vehicle
ParkingLot [1] --> [1] PricingStrategy
PricingStrategy <|.. HourlyPricingStrategy

Design Approach

Apply Singleton for the central ParkingLot manager, Factory Method for spot creation, Strategy for flexible billing calculations, and per-spot mutex locks to prevent double-booking across simultaneous entry gates.

Core Classes & Models

ParkingLot (Thread-safe Singleton orchestrator)ParkingFloor (Maintains spot collections and display counters)ParkingSpot (CompactSpot, LargeSpot, MotorcycleSpot)Vehicle (Car, Truck, Motorcycle, EVCar)Ticket (Barcode id, entry timestamp, spot reference)PricingStrategy (HourlyPricingStrategy, SurgePricingStrategy)DisplayBoard (Observer updated whenever spots are occupied/vacated)
Code Blueprint
129 lines
/**
 * Low-Level Design: Multi-Floor Parking Lot System
 * Design Patterns Applied:
 *  - Factory Method Pattern: Instantiating Vehicle and ParkingSpot instances
 *  - Strategy Pattern: Dynamic fee computation (Hourly, Daily, EV surcharge)
 *  - Singleton Pattern: Centralized ParkingLotManager orchestrator
 *  - Observer Pattern: Real-time floor occupancy boards and display monitors
 */

import java.util.*;

enum VehicleType { MOTORCYCLE, CAR, TRUCK }
enum SpotType { COMPACT, REGULAR, LARGE }

// --- [DESIGN PATTERN: Observer] Observer for parking occupancy displays ---
interface ParkingDisplayObserver {
    void onSpotOccupied(String spotId, VehicleType type);
    void onSpotVacated(String spotId);
}

class FloorDisplayBoard implements ParkingDisplayObserver {
    @Override
    public void onSpotOccupied(String spotId, VehicleType type) {
        System.out.println(">> [Display Board] Spot occupied: " + spotId + " by " + type);
    }
    @Override
    public void onSpotVacated(String spotId) {
        System.out.println(">> [Display Board] Spot freed: " + spotId);
    }
}

// --- [DESIGN PATTERN: Strategy] Dynamic fee calculation strategy ---
interface FeeStrategy {
    double calculateFee(long durationHours, VehicleType type);
}

class StandardFeeStrategy implements FeeStrategy {
    @Override
    public double calculateFee(long durationHours, VehicleType type) {
        double rate = switch (type) {
            case MOTORCYCLE -> 1.50;
            case CAR -> 3.00;
            case TRUCK -> 6.00;
        };
        return Math.max(1, durationHours) * rate;
    }
}

class ParkingSpot {
    private final String id;
    private final SpotType spotType;
    private boolean isOccupied = false;

    public ParkingSpot(String id, SpotType spotType) {
        this.id = id;
        this.spotType = spotType;
    }
    public String getId() { return id; }
    public SpotType getSpotType() { return spotType; }
    public boolean isOccupied() { return isOccupied; }
    public void setOccupied(boolean occupied) { this.isOccupied = occupied; }
}

// --- [DESIGN PATTERN: Factory Method] Vehicle & Spot factory ---
class ParkingSpotFactory {
    public static ParkingSpot createSpot(String id, VehicleType vehicleType) {
        SpotType type = switch (vehicleType) {
            case MOTORCYCLE -> SpotType.COMPACT;
            case CAR -> SpotType.REGULAR;
            case TRUCK -> SpotType.LARGE;
        };
        return new ParkingSpot(id, type);
    }
}

// --- [DESIGN PATTERN: Singleton] Central parking management system ---
class ParkingLotManager {
    private static ParkingLotManager instance;
    private final Map<String, ParkingSpot> spots = new HashMap<>();
    private final List<ParkingDisplayObserver> observers = new ArrayList<>();
    private final FeeStrategy feeStrategy;

    private ParkingLotManager() {
        this.feeStrategy = new StandardFeeStrategy();
    }

    public static synchronized ParkingLotManager getInstance() {
        if (instance == null) instance = new ParkingLotManager();
        return instance;
    }

    public void addObserver(ParkingDisplayObserver obs) { observers.add(obs); }
    public void registerSpot(ParkingSpot spot) { spots.put(spot.getId(), spot); }

    public synchronized ParkingSpot parkVehicle(String spotId, VehicleType type) {
        ParkingSpot spot = spots.get(spotId);
        if (spot != null && !spot.isOccupied()) {
            spot.setOccupied(true);
            for (ParkingDisplayObserver obs : observers) obs.onSpotOccupied(spotId, type);
            return spot;
        }
        return null;
    }

    public synchronized double unparkVehicle(String spotId, VehicleType type, long hours) {
        ParkingSpot spot = spots.get(spotId);
        if (spot != null && spot.isOccupied()) {
            spot.setOccupied(false);
            for (ParkingDisplayObserver obs : observers) obs.onSpotVacated(spotId);
            return feeStrategy.calculateFee(hours, type);
        }
        return 0.0;
    }
}

public class Main {
    public static void main(String[] args) {
        ParkingLotManager manager = ParkingLotManager.getInstance();
        manager.addObserver(new FloorDisplayBoard());

        ParkingSpot spot1 = ParkingSpotFactory.createSpot("FL1-REG-01", VehicleType.CAR);
        manager.registerSpot(spot1);

        System.out.println("--- Simulating Parking Lot ---");
        manager.parkVehicle("FL1-REG-01", VehicleType.CAR);
        double fee = manager.unparkVehicle("FL1-REG-01", VehicleType.CAR, 3);
        System.out.printf("Total Parking Fee: $%.2f\n", fee);
    }
}