My Solution for Design a Parking Lot with Score: 8/10
by iridescent_luminous693
Requirements
Parking Spot Assignment
- Assign parking spots based on vehicle size:
- Compact spots for bikes and small cars.
- Large spots for trucks and SUVs.
- Handicapped spots for vehicles with special needs.
- Ensure that the parking spot matches the size of the vehicle.
Spot Availability Checking
- Real-time tracking of parking spot availability.
- Display available spots on each floor categorized by type (compact, large, handicapped).
- Notify users when no spots are available for their vehicle type.
Vehicle Entry and Exit
- Register vehicles upon entry with details like license plate, type, and entry time.
- Update spot availability upon entry and exit.
- Remove vehicle details upon exit.
Fee Calculation
- Calculate parking fees based on the duration of parking.
- Different fee structures for different vehicle types and parking spot types.
Multi-Floor Management
- Manage parking across multiple floors with separate configurations for each floor.
- Allow dynamic allocation of spot types on each floor.
User Roles
- Admin: Configure parking lot, update pricing policies, and manage floors and spots.
- Attendant: Assist with vehicle entry and exit, and check availability.
- User: Park and retrieve their vehicles.
Scalability
- Support the addition of new floors, parking spots, and dynamic reallocation of spot types.
Error Handling
- Handle cases like:
- Attempt to park a vehicle when no spots are available.
- Invalid vehicle details during entry or exit.
Define Core Objects
Based on the requirements and use cases we've identified, the main objects of the system will be:
Analyse Relationships
ParkingLot and ParkingFloor
- Relationship: Aggregation
ParkingLot
contains multipleParkingFloor
objects.- The
ParkingLot
is responsible for managing the overall parking facility, such as adding or removing floors.
- Interaction:
- The
ParkingLot
delegates the task of finding an available parking spot to one of itsParkingFloor
objects. - The
ParkingLot
aggregates data from allParkingFloor
objects to calculate overall availability and generate reports.
- The
ParkingFloor and ParkingSpot
- Relationship: Aggregation
- Each
ParkingFloor
contains multipleParkingSpot
objects. - The
ParkingFloor
acts as a container and manager for the spots it contains.
- Each
- Interaction:
- When a user requests parking, the
ParkingFloor
finds an available spot of the correct type (Compact
,Large
, etc.). - The
ParkingFloor
updates the spot’s status (occupied/vacant) when a vehicle is parked or retrieved.
- When a user requests parking, the
ParkingSpot and Vehicle
- Relationship: Association
- A
ParkingSpot
is associated with aVehicle
when it is occupied. - If a
ParkingSpot
is vacant, it has no associatedVehicle
.
- A
- Interaction:
- When a vehicle enters the parking lot, the
ParkingSpot
is assigned to theVehicle
, storing a reference to it. - When the vehicle exits, the spot’s status is updated, and the reference to the vehicle is cleared.
- When a vehicle enters the parking lot, the
Vehicle and Ticket
- Relationship: Association
- A
Ticket
is associated with a specificVehicle
. - The ticket records information like the entry time, the assigned
ParkingSpot
, and the vehicle’s license plate.
- A
- Interaction:
- When a vehicle is parked, a
Ticket
is generated for the vehicle. - The
Ticket
acts as proof of parking and is later used to calculate the fee.
- When a vehicle is parked, a
Ticket and Payment
- Relationship: Association
- A
Payment
is associated with aTicket
. - The
Payment
records the details of the transaction, such as the amount paid and the payment time.
- A
- Interaction:
- When the vehicle exits, the system calculates the fee using the
Ticket
and processes aPayment
for it. - The
Payment
marks theTicket
as settled, completing the parking session.
- When the vehicle exits, the system calculates the fee using the
User and ParkingLot System
- Relationship: Association
- Users interact with the parking lot system through their roles:
- Admin: Configures the parking lot, updates fee structures, and monitors overall performance.
- Attendant: Assists users, updates spot statuses, and handles check-ins/check-outs.
- Regular User: Parks and retrieves vehicles, views available spots, and makes payments.
- Users interact with the parking lot system through their roles:
- Interaction:
- Users initiate actions such as spot reservation, vehicle check-in, or check-out, which are routed through the system.
- The system validates the user’s role to determine which actions are permissible.
Establish Hierarchy
1. ParkingSpot
- Base Class:
ParkingSpot
- Attributes:
spotId
,type
,isOccupied
,vehicle
- Methods:
assignVehicle(Vehicle v)
,removeVehicle()
- Attributes:
- Subclasses:
- CompactSpot
- Specific for small vehicles like bikes and small cars.
- LargeSpot
- For larger vehicles like trucks and SUVs.
- HandicappedSpot
- Reserved for vehicles with special needs.
- ElectricSpot
- For electric vehicles with charging capabilities.
- CompactSpot
2. Vehicle
- Base Class:
Vehicle
- Attributes:
licensePlate
,type
- Attributes:
- Subclasses:
- Car
- Default for regular passenger cars.
- Bike
- For two-wheeled vehicles.
- Truck
- For large cargo or heavy-duty vehicles.
- Car
3. User
- Base Class:
User
- Attributes:
userId
,name
,role
- Methods:
parkVehicle(Vehicle v)
,retrieveVehicle(String licensePlate)
- Attributes:
- Subclasses:
- Admin
- Additional responsibilities:
- Configure parking lot (add/remove floors, spots).
- Update pricing policies.
- Additional responsibilities:
- Attendant
- Additional responsibilities:
- Assist users with parking and retrieval.
- Update parking spot statuses.
- Additional responsibilities:
- RegularUser
- Responsibilities:
- Park and retrieve vehicles.
- View parking history.
- Responsibilities:
- Admin
4. Ticket and Payment
- Ticket
- A standalone class with attributes:
ticketId
,vehicle
,parkingSpot
,entryTime
,exitTime
,fee
- Methods:
calculateFee()
- A standalone class with attributes:
- Payment
- A standalone class with attributes:
paymentId
,amount
,paymentTime
,ticket
- Methods:
processPayment(Ticket ticket, double amount)
- A standalone class with attributes:
Design Patterns
1. Singleton Pattern
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;
}
}
2. Factory Pattern
The Factory Pattern can be used to create different types of ParkingSpot
and Vehicle
objects based on input.
ParkingSpot Factory
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");
}
}
}
Vehicle Factory
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");
}
}
}
3. Strategy Pattern
The Strategy Pattern can be used for calculating parking fees based on different vehicle types or spot types.
Fee Calculation Strategies
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
}
}
Fee Calculator Class
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);
}
}
4. Observer Pattern
The Observer Pattern can be used to notify Attendants
or Users
about parking spot availability changes.
Observer Interface
public interface Observer {
void update(String message);
}
Subject Interface
public interface Subject {
void registerObserver(Observer observer);
void removeObserver(Observer observer);
void notifyObservers(String message);
}
ParkingFloor as a Subject
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);
}
}
}
Attendant as an Observer
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);
}
}
5. Builder Pattern
The Builder Pattern can be used to construct complex objects like ParkingLot
.
ParkingLotBuilder
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;
}
}
Define Class Members (write code)
1. 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;
}
}
2. ParkingFloor
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;
}
}
3. ParkingSpot
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");
}
}
4. Vehicle
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");
}
}
5. Ticket
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;
}
}
6. Payment
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;
}
}
Adhere to SOLID Guidelines
1. Single Responsibility Principle (SRP)
Definition: A class should have only one reason to change, meaning it should only have one responsibility.
Analysis:
- Adheres:
- Each class has a single, well-defined responsibility:
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.
- Responsibilities are clearly separated.
- Each class has a single, well-defined responsibility:
- Improvement:
- If the system grows, consider separating
Fee Calculation
logic into a dedicated service (e.g.,FeeCalculator
).
- If the system grows, consider separating
2. Open/Closed Principle (OCP)
Definition: A class should be open for extension but closed for modification.
Analysis:
- Adheres:
- The
ParkingSpot
andVehicle
hierarchies allow adding new types (e.g.,MotorcycleSpot
,Bus
) without modifying existing classes. - The fee calculation logic in
Ticket
can be extended using aFeeCalculator
strategy without modifying theTicket
class itself.
- The
- Improvement:
- Introduce the Strategy Pattern for fee calculation. This would allow adding new pricing models (e.g., discounted rates) without modifying the
Ticket
class.
- Introduce the Strategy Pattern for fee calculation. This would allow adding new pricing models (e.g., discounted rates) without modifying the
3. Liskov Substitution Principle (LSP)
Definition: Objects of a superclass should be replaceable with objects of its subclasses without altering the correctness of the program.
Analysis:
- Adheres:
- The
ParkingSpot
subclasses (CompactSpot
,LargeSpot
, etc.) can replace theParkingSpot
base class wherever it is used. - The
Vehicle
subclasses (Car
,Bike
,Truck
) adhere to the same interface and can be substituted without issues.
- The
4. Interface Segregation Principle (ISP)
Definition: A class should not be forced to implement interfaces it does not use.
Analysis:
- Adheres:
- No large interfaces are imposed; classes only contain attributes and methods relevant to their responsibilities.
- All methods in the classes are directly related to their intended role (e.g.,
assignVehicle
forParkingSpot
orprocessPayment
forPayment
).
5. Dependency Inversion Principle (DIP)
Definition: High-level modules should not depend on low-level modules but on abstractions.
Analysis:
- Adheres:
- The
ParkingLot
andParkingFloor
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.
- The
- Improvement:
- Currently, the fee calculation logic is directly implemented in
Ticket
. Using aFeeCalculator
abstraction would better adhere to DIP by decoupling fee calculation from theTicket
class.
- Currently, the fee calculation logic is directly implemented in