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);
}
}