ParkingLot: top-level facade. Holds the floors, entry/exit gates and a PricingStrategy. Orchestrates parkVehicle() and exitVehicle()
ParkingFloor: one level; holds a list of ParkingSpots. Finds an available spot of a requested size
ParkingSpot: a single spot with a SpotType (SMALL/MEDIUM/LARGE) and an isAvailable flag. Can be occupied/released
Vehicle (abstract) -> Car, Motorcycle, Truck - each subclass knows its VehicleType
ParkingTicket: issued on entry; records the vehicle, spot and entry timestamp; used for fee calc at exit
Pricing Strategy (interface): HourlyStrategy, FlatRateStrategy implement fee calculation
Relationships & Behavior
Composition: ParkingLot owns ParkingFloor(s); ParkingFloor owns ParkingSpot(s). If the lot dies, floors/spots die with it
Inheritance: Car/Motorcycle/Truck extend abstract Vehicle
Association: ParkingTicket references a Vehicle and a ParkingSpot (doesn't own them)
Strategy: ParkingLot depends on the PricingStrategy interface, never a concrete rate class - so weekend pricing can be swapped in later without touching ParkingLot
For each class, define the attributes (data) it will hold and the methods (functions) that operate on the attributes. Ensure they align with the object's responsibilities and adhere to the principle of encapsulation. Write your code in the code editor below.
Single Responsibility Principle (SRP): Every class has one reason to change.
ParkingSpot only manages its own state and invariant (canFit).ParkingFloor only handles collection search logic (findSpotForVehicle).ParkingTicket is purely a data container for the session.ParkingLot is the orchestrator that ties the subsystems together.Open/Closed Principle (OCP): The system is open for extension but closed for modification. I built two major seams for this:
WeekendPricingStrategy, I just create a new class implementing the interface. ParkingLot remains entirely untouched.OversizedTruck, I create a subclass and add it to the VehicleSize rank map. The canFit and floor search logic never has to change.Liskov Substitution Principle (LSP): Any subclass of Vehicle (Motorcycle, Car, Truck) can be passed into spot.park(vehicle) or floor.findSpotForVehicle() interchangeably without breaking the application logic.
Dependency Inversion Principle (DIP): ParkingLot depends on the PricingStrategy abstraction (interface), not the concrete HourlyPricingStrategy. This is injected via the constructor, which also makes the lot easily mockable for unit testing.
enter() method uses an in-memory busy-wait flag to serialize entry. This successfully prevents double-booking if multiple gates are running on this exact machine, but it is a coarse, single-process lock."Map for activeTickets with a highly concurrent structure (like a Redis hash or a ConcurrentHashMap in Java) so gates can read and write tickets simultaneously without a global mutex."SELECT ... FOR UPDATE) on the parking spot to guarantee that two distributed gates cannot claim the same spot at the exact same millisecond."Extensibility & Future Improvements
Wrap up the deep dive by throwing out a few one-liners that show you can anticipate product requirements before they are asked:
ElectricSpot class that extends ParkingSpot with a ChargingStation object, and add a per-kWh surcharge to a new EVPricingStrategy."Reservation class that pre-allocates a spot for a specific time window, with a background worker thread that expires stale or no-show reservations."DynamicPricingStrategy that queries the isFull() capacity and automatically applies surge pricing when the lot hits 90% capacity."