Parking Spot Assignment
Spot Availability Checking
Vehicle Entry and Exit
Fee Calculation
Multi-Floor Management
User Roles
Scalability
Error Handling
Based on the requirements and use cases we've identified, the main objects of the system will be:
ParkingLot and ParkingFloor
ParkingLot contains multiple ParkingFloor objects.ParkingLot is responsible for managing the overall parking facility, such as adding or removing floors.ParkingLot delegates the task of finding an available parking spot to one of its ParkingFloor objects.ParkingLot aggregates data from all ParkingFloor objects to calculate overall availability and generate reports.ParkingFloor and ParkingSpot
ParkingFloor contains multiple ParkingSpot objects.ParkingFloor acts as a container and manager for the spots it contains.ParkingFloor finds an available spot of the correct type (Compact, Large, etc.).ParkingFloor updates the spot’s status (occupied/vacant) when a vehicle is parked or retrieved.ParkingSpot and Vehicle
ParkingSpot is associated with a Vehicle when it is occupied.ParkingSpot is vacant, it has no associated Vehicle.ParkingSpot is assigned to the Vehicle, storing a reference to it.Vehicle and Ticket
Ticket is associated with a specific Vehicle.ParkingSpot, and the vehicle’s license plate.Ticket is generated for the vehicle.Ticket acts as proof of parking and is later used to calculate the fee.Ticket and Payment
Payment is associated with a Ticket.Payment records the details of the transaction, such as the amount paid and the payment time.Ticket and processes a Payment for it.Payment marks the Ticket as settled, completing the parking session.User and ParkingLot System
ParkingSpotspotId, type, isOccupied, vehicleassignVehicle(Vehicle v), removeVehicle()VehiclelicensePlate, typeUseruserId, name, roleparkVehicle(Vehicle v), retrieveVehicle(String licensePlate)ticketId, vehicle, parkingSpot, entryTime, exitTime, feecalculateFee()paymentId, amount, paymentTime, ticketprocessPayment(Ticket ticket, double amount)The ParkingLot class should use the Singleton Pattern to ensure only one instance of the parking lot exists.
public class ParkingLot {
private static ParkingLot instance;
private List<ParkingFloor> floors;
private ParkingLot() {
floors = new ArrayList<>();
}
public static synchronized ParkingLot getInstance() {
if (instance == null) {
instance = new ParkingLot();
}
return instance;
}
public void addFloor(ParkingFloor floor) {
floors.add(floor);
}
public List<ParkingFloor> getFloors() {
return floors;
}
}
The Factory Pattern can be used to create different types of ParkingSpot and Vehicle objects based on input.
public class ParkingSpotFactory {
public static ParkingSpot createSpot(String type, String spotId) {
switch (type.toLowerCase()) {
case "compact":
return new CompactSpot(spotId);
case "large":
return new LargeSpot(spotId);
case "handicapped":
return new HandicappedSpot(spotId);
case "electric":
return new ElectricSpot(spotId);
default:
throw new IllegalArgumentException("Unknown parking spot type");
}
}
}
public class VehicleFactory {
public static Vehicle createVehicle(String type, String licensePlate) {
switch (type.toLowerCase()) {
case "car":
return new Car(licensePlate);
case "bike":
return new Bike(licensePlate);
case "truck":
return new Truck(licensePlate);
default:
throw new IllegalArgumentException("Unknown vehicle type");
}
}
}
The Strategy Pattern can be used for calculating parking fees based on different vehicle types or spot types.
public interface FeeCalculationStrategy {
double calculateFee(long durationInHours);
}
public class CompactSpotFeeStrategy implements FeeCalculationStrategy {
@Override
public double calculateFee(long durationInHours) {
return durationInHours * 10.0; // $10/hour
}
}
public class LargeSpotFeeStrategy implements FeeCalculationStrategy {
@Override
public double calculateFee(long durationInHours) {
return durationInHours * 15.0; // $15/hour
}
}
public class HandicappedSpotFeeStrategy implements FeeCalculationStrategy {
@Override
public double calculateFee(long durationInHours) {
return durationInHours * 5.0; // $5/hour
}
}
public class ElectricSpotFeeStrategy implements FeeCalculationStrategy {
@Override
public double calculateFee(long durationInHours) {
return durationInHours * 12.0; // $12/hour
}
}
public class FeeCalculator {
private FeeCalculationStrategy strategy;
public void setStrategy(FeeCalculationStrategy strategy) {
this.strategy = strategy;
}
public double calculateFee(long durationInHours) {
if (strategy == null) {
throw new IllegalStateException("Fee strategy not set");
}
return strategy.calculateFee(durationInHours);
}
}
The Observer Pattern can be used to notify Attendants or Users about parking spot availability changes.
public interface Observer {
void update(String message);
}
public interface Subject {
void registerObserver(Observer observer);
void removeObserver(Observer observer);
void notifyObservers(String message);
}
import java.util.ArrayList;
import java.util.List;
public class ParkingFloor implements Subject {
private List<Observer> observers = new ArrayList<>();
private List<ParkingSpot> spots;
public ParkingFloor() {
spots = new ArrayList<>();
}
public void addSpot(ParkingSpot spot) {
spots.add(spot);
}
public void spotStatusChanged(String message) {
notifyObservers(message);
}
@Override
public void registerObserver(Observer observer) {
observers.add(observer);
}
@Override
public void removeObserver(Observer observer) {
observers.remove(observer);
}
@Override
public void notifyObservers(String message) {
for (Observer observer : observers) {
observer.update(message);
}
}
}
public class Attendant implements Observer {
private String name;
public Attendant(String name) {
this.name = name;
}
@Override
public void update(String message) {
System.out.println("Attendant " + name + " received notification: " + message);
}
}
The Builder Pattern can be used to construct complex objects like ParkingLot.
public class ParkingLotBuilder {
private ParkingLot parkingLot;
public ParkingLotBuilder() {
this.parkingLot = ParkingLot.getInstance();
}
public ParkingLotBuilder addFloor(ParkingFloor floor) {
parkingLot.addFloor(floor);
return this;
}
public ParkingLot build() {
return parkingLot;
}
}
import java.util.ArrayList;
import java.util.List;
public class ParkingLot {
private static ParkingLot instance;
private List<ParkingFloor> floors;
private int totalSpots;
private int availableSpots;
private ParkingLot() {
floors = new ArrayList<>();
totalSpots = 0;
availableSpots = 0;
}
public static synchronized ParkingLot getInstance() {
if (instance == null) {
instance = new ParkingLot();
}
return instance;
}
public void addFloor(ParkingFloor floor) {
floors.add(floor);
totalSpots += floor.getTotalSpots();
availableSpots += floor.getAvailableSpots();
}
public ParkingSpot assignSpot(Vehicle vehicle) {
for (ParkingFloor floor : floors) {
ParkingSpot spot = floor.findAvailableSpot(vehicle.getType());
if (spot != null) {
availableSpots--;
return spot;
}
}
return null;
}
public void releaseSpot(ParkingSpot spot) {
for (ParkingFloor floor : floors) {
if (floor.releaseSpot(spot)) {
availableSpots++;
return;
}
}
}
public int getAvailableSpots(String type) {
int count = 0;
for (ParkingFloor floor : floors) {
count += floor.getAvailableSpotsByType(type);
}
return count;
}
}
import java.util.ArrayList;
import java.util.List;
public class ParkingFloor {
private int level;
private List<ParkingSpot> spots;
public ParkingFloor(int level) {
this.level = level;
this.spots = new ArrayList<>();
}
public void addSpot(ParkingSpot spot) {
spots.add(spot);
}
public ParkingSpot findAvailableSpot(String vehicleType) {
for (ParkingSpot spot : spots) {
if (!spot.isOccupied() && spot.getType().equalsIgnoreCase(vehicleType)) {
return spot;
}
}
return null;
}
public boolean releaseSpot(ParkingSpot spot) {
if (spots.contains(spot)) {
spot.removeVehicle();
return true;
}
return false;
}
public int getTotalSpots() {
return spots.size();
}
public int getAvailableSpots() {
int count = 0;
for (ParkingSpot spot : spots) {
if (!spot.isOccupied()) {
count++;
}
}
return count;
}
public int getAvailableSpotsByType(String type) {
int count = 0;
for (ParkingSpot spot : spots) {
if (!spot.isOccupied() && spot.getType().equalsIgnoreCase(type)) {
count++;
}
}
return count;
}
}
public abstract class ParkingSpot {
private String spotId;
private String type;
private boolean isOccupied;
private Vehicle vehicle;
public ParkingSpot(String spotId, String type) {
this.spotId = spotId;
this.type = type;
this.isOccupied = false;
}
public void assignVehicle(Vehicle vehicle) {
this.vehicle = vehicle;
this.isOccupied = true;
}
public void removeVehicle() {
this.vehicle = null;
this.isOccupied = false;
}
public boolean isOccupied() {
return isOccupied;
}
public String getType() {
return type;
}
}
class CompactSpot extends ParkingSpot {
public CompactSpot(String spotId) {
super(spotId, "Compact");
}
}
class LargeSpot extends ParkingSpot {
public LargeSpot(String spotId) {
super(spotId, "Large");
}
}
class HandicappedSpot extends ParkingSpot {
public HandicappedSpot(String spotId) {
super(spotId, "Handicapped");
}
}
class ElectricSpot extends ParkingSpot {
public ElectricSpot(String spotId) {
super(spotId, "Electric");
}
}
public abstract class Vehicle {
private String licensePlate;
private String type;
public Vehicle(String licensePlate, String type) {
this.licensePlate = licensePlate;
this.type = type;
}
public String getLicensePlate() {
return licensePlate;
}
public String getType() {
return type;
}
}
class Car extends Vehicle {
public Car(String licensePlate) {
super(licensePlate, "Compact");
}
}
class Bike extends Vehicle {
public Bike(String licensePlate) {
super(licensePlate, "Compact");
}
}
class Truck extends Vehicle {
public Truck(String licensePlate) {
super(licensePlate, "Large");
}
}
import java.util.Date;
public class Ticket {
private String ticketId;
private Vehicle vehicle;
private ParkingSpot spot;
private Date entryTime;
private Date exitTime;
private double fee;
public Ticket(String ticketId, Vehicle vehicle, ParkingSpot spot) {
this.ticketId = ticketId;
this.vehicle = vehicle;
this.spot = spot;
this.entryTime = new Date();
}
public void setExitTime() {
this.exitTime = new Date();
}
public double calculateFee(long hourlyRate) {
long duration = (exitTime.getTime() - entryTime.getTime()) / (1000 * 60 * 60);
duration = Math.max(duration, 1);
fee = duration * hourlyRate;
return fee;
}
}
import java.util.Date;
public class Payment {
private String paymentId;
private double amount;
private Date paymentTime;
private Ticket ticket;
public Payment(String paymentId, Ticket ticket) {
this.paymentId = paymentId;
this.ticket = ticket;
this.amount = ticket.calculateFee(10);
this.paymentTime = new Date();
}
public boolean processPayment() {
System.out.println("Payment of $" + amount + " processed successfully.");
return true;
}
}
Definition: A class should have only one reason to change, meaning it should only have one responsibility.
Analysis:
ParkingLot: Manages the overall parking system.ParkingFloor: Handles parking spots on a specific floor.ParkingSpot: Represents individual parking spots.Vehicle: Represents vehicles with basic attributes.Ticket: Tracks parking sessions and calculates fees.Payment: Handles payment processing for tickets.Fee Calculation logic into a dedicated service (e.g., FeeCalculator).Definition: A class should be open for extension but closed for modification.
Analysis:
ParkingSpot and Vehicle hierarchies allow adding new types (e.g., MotorcycleSpot, Bus) without modifying existing classes.Ticket can be extended using a FeeCalculator strategy without modifying the Ticket class itself.Ticket class.Definition: Objects of a superclass should be replaceable with objects of its subclasses without altering the correctness of the program.
Analysis:
ParkingSpot subclasses (CompactSpot, LargeSpot, etc.) can replace the ParkingSpot base class wherever it is used.Vehicle subclasses (Car, Bike, Truck) adhere to the same interface and can be substituted without issues.Definition: A class should not be forced to implement interfaces it does not use.
Analysis:
assignVehicle for ParkingSpot or processPayment for Payment).Definition: High-level modules should not depend on low-level modules but on abstractions.
Analysis:
ParkingLot and ParkingFloor depend on abstractions (e.g., ParkingSpot, Vehicle) rather than concrete implementations. This allows adding new spot or vehicle types without modifying the high-level classes.Ticket. Using a FeeCalculator abstraction would better adhere to DIP by decoupling fee calculation from the Ticket class.