Requirements

Determine the different ways the system will be used. This includes main functions the system needs to perform and who will use it.


The system will have user which can checkin into the parking and checkout, paying the fee for the service. Then there will be parking admin who is responsible for finding accurate parking, assigning parking spot, freeing the space and calculating the parking fee.


Define Core Objects

Based on the requirements and use cases, identify the main objects of the system...

  • Model
    • ParkingSpot class
    • Vehicle class
    • ParkingSpotType Enum
    • VehicleType Enum
    • Floor Enum
  • Controller
    • Parking Admin class
  • Repository
    • Vehicle Manager singleton class
    • Parking Manager singleton class
  • Main class



Analyze Relationships

Determine how these objects will interact with each other to fulfill the use cases...


  1. user checks in
    1. Main class receives a request from Vehicle object to checkin with requirements
    2. Parking Admin finds the vacant spot via Parking Manager class
      1. If available, Parking Admin assigns that seat
      2. If not, returns the error to try again after some time.
  2. user checks out
    1. Main class receives a request from Vehicle object to checkout
    2. Parking Admin validates if the parking spot was occupied by this vehicle, release it and share the parking fee.



Establish Hierarchy

Design inheritance trees where applicable to promote code reuse and polymorphism. This step involves identifying common attributes and behaviors that can be abstracted into parent classes...


Parking Spot can be parent class. All the car types are its child class and all can implement their own calculate charge method.



Design Patterns

Consider using design patterns (e.g., Factory, Singleton, Observer, Strategy) that fit the problem...


VehicleService and ParkingService will be Singleton



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 class Car { ... } // Example public class Main { public static void main(String[] args) { try { ParkingAdmin admin = new ParkingAdmin(); // add 5 parking spots admin.addParkingSpots(new ParkingSpot()); admin.addParkingSpots(new ParkingSpot()); admin.addParkingSpots(new ParkingSpot()); admin.addParkingSpots(new ParkingSpot()); admin.addParkingSpots(new ParkingSpot()); // checkin vehicle Vehicle vehicle = new Vehicle("9473447953", "123", "CAR"); admin.checkin(vehicle); vehicle = new Vehicle("9473447954", "123", "CAR"); admin.checkin(vehicle); vehicle = new Vehicle("9473447956", "123", "CAR"); admin.checkin(vehicle); vehicle = new Vehicle("9473447950", "123", "CAR"); admin.checkin(vehicle); vehicle = new Vehicle("9473447957", "123", "CAR"); admin.checkin(vehicle); // checkout vehicle admin.checkout("9473447953"); } catch (Exception e) { System.out.println(e.getMessage()); } } }


public class ParkingLotManager { Map<String, ParkingSpot> parkingMap; List<ParkingSpot> parkingSpotList; private static ParkingLotManager instance; private ParkingLotManager() { parkingMap = new HashMap&lt;&gt;(); parkingSpotList = new ArrayList&lt;&gt;(); } public static ParkingLotManager getInstance() { if (instance == null) { instance = new ParkingLotManager(); } return instance; } public void updateParking(ParkingSpot parking) { parkingSpotList.add(parking); parkingMap.put(parking.getParkingId(), parking); } public ParkingSpot getParking(String parkingId) { if (parkingMap.containsKey(parkingId)) { return parkingMap.get(parkingId); } else { throw new RuntimeException(&quot;Parking Spot not registered.&quot;); } } public List&lt;ParkingSpot&gt; getParkingSpotList(SpotType spotType, int floorId, boolean vacantOnly) { List&lt;ParkingSpot&gt; requestedTypeParkingSpot = new ArrayList&lt;&gt;(); for (ParkingSpot spot: parkingSpotList) { if (spot.getParkingType() == spotType &amp;&amp; spot.getFloorId() == floorId) { if (vacantOnly) { if (spot.isVacant()) requestedTypeParkingSpot.add(spot); } else { requestedTypeParkingSpot.add(spot); } } } return requestedTypeParkingSpot; } public List&lt;ParkingSpot&gt; getParkingSpotList(SpotType spotType, boolean vacantOnly) { List&lt;ParkingSpot&gt; requestedTypeParkingSpot = new ArrayList&lt;&gt;(); for (ParkingSpot spot: parkingSpotList) { if (spot.getParkingType() == spotType) { if (vacantOnly) { if (spot.isVacant()) requestedTypeParkingSpot.add(spot); } else { requestedTypeParkingSpot.add(spot); } } } return requestedTypeParkingSpot; } public List&lt;ParkingSpot&gt; getParkingSpotList(boolean vacantOnly) { List&lt;ParkingSpot&gt; requestedTypeParkingSpot = new ArrayList&lt;&gt;(); for (ParkingSpot spot: parkingSpotList) { if (vacantOnly) { if (spot.isVacant()) requestedTypeParkingSpot.add(spot); } else { requestedTypeParkingSpot.add(spot); } } return requestedTypeParkingSpot; } public List&lt;ParkingSpot&gt; getParkingSpotList() { return getParkingSpotList(false); } }



public class VehicleRegistery { Map<String, Vehicle> userMap; Map<String, String> userParkingSpotMap; private static VehicleRegistery instance; private VehicleRegistery() { userMap = new HashMap&lt;&gt;(); userParkingSpotMap = new HashMap&lt;&gt;(); } public static VehicleRegistery getInstance() { if (instance == null) { instance = new VehicleRegistery(); } return instance; } public void updateUser(Vehicle vehicle) { userMap.put(vehicle.getUserNumber(), vehicle); } public Vehicle getUser(String userNumber) { if (userMap.containsKey(userNumber)) { return userMap.get(userNumber); } else { throw new RuntimeException(&quot;Vehicle not registered.&quot;); } } public void updateMapping(String userNumber, String parkingId) { userParkingSpotMap.put(userNumber, parkingId); } public String getParkingSpot(String userNumber) { if (userParkingSpotMap.containsKey(userNumber)) { return userParkingSpotMap.get(userNumber); } else { throw new RuntimeException(&quot;Vehicle not checkedIn.&quot;); } } }



public class ParkingAdmin { private final VehicleRegistery vehicleRegistery; private final ParkingLotManager parkingLotManager; public ParkingAdmin() { vehicleRegistery = VehicleRegistery.getInstance(); parkingLotManager = ParkingLotManager.getInstance(); } public void addParkingSpots(ParkingSpot parkingSpot) { parkingLotManager.updateParking(parkingSpot); } public String checkin(Vehicle vehicle) { String userNumber = vehicle.getUserNumber(); LocalDateTime checkinTime = vehicle.setEntryTime(); List&lt;ParkingSpot&gt; availableSpots = parkingLotManager.getParkingSpotList(true); if (availableSpots.isEmpty()) { throw new RuntimeException(&quot;Parking Spots not available! Please try again after some time.&quot;); } ParkingSpot parkingSpot = availableSpots.get(0); System.out.println(userNumber + &quot; allocated &quot; + parkingSpot.getParkingId().substring(0, 6) + &quot; at &quot; + checkinTime); allocateSpot(parkingSpot.getParkingId()); vehicleRegistery.updateUser(vehicle); vehicleRegistery.updateMapping(userNumber, parkingSpot.getParkingId()); return parkingSpot.getParkingId(); } private void allocateSpot(String parkingId) { ParkingSpot parkingSpot = parkingLotManager.getParking(parkingId); parkingSpot.setVacant(false); parkingLotManager.updateParking(parkingSpot); System.out.println(parkingId + &quot; is occupied now!&quot;); System.out.println(); } public int checkout(String userNumber) { Vehicle vehicle = vehicleRegistery.getUser(userNumber); LocalDateTime exitTime = LocalDateTime.now(); String parkingId = vehicleRegistery.getParkingSpot(userNumber); ParkingSpot parkingSpot = parkingLotManager.getParking(parkingId); Duration duration = Duration.between(vehicle.getEntryTime(), exitTime); int charge = calculateCharge(duration.toHours(), parkingSpot.getCharge()); System.out.println(userNumber + &quot; needs to pay &quot; + charge); vacateSpot(parkingId); return charge; } private void vacateSpot(String parkingId) { ParkingSpot parkingSpot = parkingLotManager.getParking(parkingId); parkingSpot.setVacant(true); parkingLotManager.updateParking(parkingSpot); System.out.println(parkingId + &quot; is vacant now!&quot;); } private int calculateCharge(long timePeriod, int charge) { if (timePeriod == 1L) return charge; else if (timePeriod == 2L) return 2 * charge; else { timePeriod -= 2L; return (int) (2 * charge + (0.5 * charge * timePeriod)); } } }



public class ParkingSpot { String parkingId = null; SpotType parkingType = null; int floorId = 0; boolean isVacant = true; public ParkingSpot() { parkingId = UUID.randomUUID().toString(); }; public SpotType getParkingType() { return parkingType; } public boolean isVacant() { return isVacant; } public void setVacant(Boolean vacant) { isVacant = vacant; } public String getParkingId() { return parkingId; } public int getFloorId() { return floorId; } public int getCharge() { return 100; } }



public class BusParkingSpot extends ParkingSpot { @Override public int getCharge() { return 200; } }



public class CarParkingSpot extends ParkingSpot { @Override public int getCharge() { return 100; } }



public class ParkingSpot { String parkingId = null; SpotType parkingType = null; int floorId = 0; boolean isVacant = true; public ParkingSpot() { parkingId = UUID.randomUUID().toString(); }; public SpotType getParkingType() { return parkingType; } public boolean isVacant() { return isVacant; } public void setVacant(Boolean vacant) { isVacant = vacant; } public String getParkingId() { return parkingId; } public int getFloorId() { return floorId; } public int getCharge() { return 100; } }



public enum SpotType { COMPACT, LARGE, HANDICAPPED }


public class TruckParkingSpot extends ParkingSpot { @Override public int getCharge() { return 300; } }



public enum VehicleType { CAR, BIKE, TRUCK }



public class Vehicle { String userId; String userNumber; String vehicleNumber; VehicleType vehicleType; LocalDateTime entryTime; LocalDateTime exitTime; int parkingCharge; ParkingSpot parkingSpot; public Vehicle(String userNumber, String vehicleNumber, String vehicleType) { this.userNumber = userNumber; this.vehicleNumber = vehicleNumber; this.vehicleType = VehicleType.valueOf(vehicleType); this.userId = UUID.randomUUID().toString(); this.entryTime = LocalDateTime.now(); } public String getUserNumber() { return userNumber; } public LocalDateTime getEntryTime() { return entryTime; } public LocalDateTime setEntryTime() { entryTime = LocalDateTime.now(); return entryTime; } public void setParkingCharge(int parkingCharge) { this.parkingCharge = parkingCharge; } }




Adhere to SOLID Guidelines

Check and explain whether your design adheres to solid principles (Ask interviewer what SOLID principle is if you can not recall it.)...






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

Critically examine your design for any flaws or areas for future improvement...