Requirements

  • Parking lot has parking spots.
  • Parking lot should check available spots per floor, and should display on the screen at the entrance.
  • Driver can freely choose where to park.
  • Spots have its type, like for small vehicles, standard, heavy, and for handicapped.
  • Each spot has different cost type.
  • The cost on the car starts right after the car enters the parking lot.
  • All cars should pay when they try to go out to exit.


Define Core Objects

  • Spot - Spot abstract class. All classes are spot types.
  • Vehicle - Abstract class for cars which entered into parking lot. Children implementations are "type" of the vechicle.


Two abstract classes above are core objects to show the relation between spot and vehicle.


  • FloorManager - responsible for managing which spots are available, allocated, and etc. at the certain floor. FloorManager manages cars to park or unpark to designated spot.
  • ParkingLotService - manages whole parking lot. It has responsible for managing each floor via FloorManager class. It stores the time when the vehicle enters the parking lot, and calculate the fee when exists the parking lot on exit method.
    • ParkingLotService can show available spots via getAvailableSpotsOnFloor method.






Analyze Relationships


If a vehicle parks it requires Spot, while Spot can be instantiated stand alone.

If a vehicle does not park, just roaming around to locate parking spot, Spot can be null.


ParkingLotService is the top manager service of managing all floors, spots, and entered vehicles.

FloorManager will be instantiated per each floor, from ParkingLotService, and will be injected to ParkingLotService.

Spot will be instantiated per each spots on specific floor, from FloorManager, and will be injected to FloorManager.



Establish Hierarchy


Refer to diagram





Design Patterns


  • composite pattern for structuring hierarchical classes like ParkingLotService, FloorManager
  • Singleton pattern for ParkingLotService since it is the main center class to manage all floors and spots of the parking lot.




Define Class Members (write code)

Attributes: For each class, define the attributes (data) it will hold...

Methods: Define the methods (functions) that operate on the attributes. Ensure they align with the object's responsibilities and adhere to the principle of encapsulation.



public abstract class Spot { private int costPerHour; private Vehicle parkedVehicle; private LocalDateTime parkedAt; public assignVehicle(Vehicle vehicle) { final LocalDateTime now = LocalDateTime.now(); // check if available.. this.parkedAt = now; this.parkedVehicle = vehicle; this.parkedVehicle.park(this); } public removeVehicle() { this.parkedAt = null; this.parkedVehicle.leaveSpot(); } }


public enum VehicleType { COMPACT(1000), STANDARD(2000), HEAVY(3000); private final int costPerHour; public VehicleType(int costPerHour) { this.costPerHour = costPerHour; } } public abstract class Vehicle { private final VehicleType type; private final LocalDateTime enteredAt; private Spot parkedAt = null; public Vehicle(LocalDateTime enteredAt) { this.enteredAt = enteredAt; } public void park(Spot spot) { final LocalDateTime now = LocalDateTime.now(); // check if parked... this.enteredAt = now; this.parkedAt = spot; } }


public class FloorManager { private final List<Spot> spots; public FloorManager(List<Spot> spots) { this.spots = spots; } public List<Spot> getAvailableSpots() { return this.spots.stream() .filter(spot -> spot.isAvailable()) .toList(); } public void park(Vehicle vehicle, Spot spot) { spot.assignVehicle(vehicle); } public void unpark(Vehicle vehicle) { Spot spot = findSpotByVehicle(vehicle); spot.removeVehicle(); vehicle.leaveSpot(); } public Spot findSpotByVehicle(Vehicle vehicle) { return this.spots.stream() .findFirst(spot -> spot.parkedVehicle == vehicle) .orElseThrow(); } }


public class ParkingLotService { private final List<FloorManager> floorManagers; private List<Vehicle> enteredVehicles = new ArrayList<>(); public List<Spot> getAvailaleSpotsOnFloor(int floor) { // for readability, assume floor starts from 0 return floorManagers.get(floor).getAvailableSpots(); } public void enter(Vehicle vehicle) { LocalDateTime now = LocalDateTime.now(); Vehicle enteredVehicle = new Vehicle(now); enteredVehicles.add(enteredVehicle); } public void exit(Vehicle vehicle) { LocalDateTime enteredAt = vehicle.getEnteredAt(); VehicleType vehicleType = vehicle.getType(); // calculate hours how much parked int parkedHour = (enteredAt - now()).toHours(); int cost = parkedHour * vehicleType.getCostPerHour(); // pay cost getFloorManagerByVehicle(vehicle).unpark(vehicle); enteredVehicles.remove(vehicle); } private FloorManager getFloorManagerByVehicle(Vehicle vehicle) { return floorManagers.stream() .findFirst(floor -> { try { floor.findSpotByVehicle(vehicle); return true; } catch(NoSuchElementException ex) { return false; } }) .orElseThrow(); } }




Adhere to SOLID Guidelines

  • Every classes are keeping Single Responsibility Principle.
    • Vehicle and Spot are responsible for its state, whether parked or not.
    • FloorManager is responsible for managing spots on the specific floor.
    • ParkingLotService is responsible for whole parking system, while it manages floors, and it does not directly references spots on each floors.





Consider Scalability and Flexibility

Explain how your design can handle changes in scale and whether it would be easily to extend with new functionalities...






Create/Explain your diagram(s)

Try creating a class, flow, state and/or sequence diagram using the diagramming tool. Mermaid flow diagrams can be used to represent system use cases. You can ask the interviewer bot to create a starter diagram if unfamiliar with the tool. Briefly explain your diagrams if necessary...






Future improvements

  • Search vehicles where it is parked.
  • Multiple entrances.