Two abstract classes above are core objects to show the relation between spot and vehicle.
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.
Refer to diagram
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();
}
}
Explain how your design can handle changes in scale and whether it would be easily to extend with new functionalities...
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...