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 multiple ParkingFloor 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 its ParkingFloor objects.
    • The ParkingLot aggregates data from all ParkingFloor objects to calculate overall availability and generate reports.

ParkingFloor and ParkingSpot

  • Relationship: Aggregation
    • Each ParkingFloor contains multiple ParkingSpot objects.
    • The ParkingFloor acts as a container and manager for the spots it contains.
  • 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.

ParkingSpot and Vehicle

  • Relationship: Association
    • A ParkingSpot is associated with a Vehicle when it is occupied.
    • If a ParkingSpot is vacant, it has no associated Vehicle.
  • Interaction:
    • When a vehicle enters the parking lot, the ParkingSpot is assigned to the Vehicle, storing a reference to it.
    • When the vehicle exits, the spot’s status is updated, and the reference to the vehicle is cleared.

Vehicle and Ticket

  • Relationship: Association
    • A Ticket is associated with a specific Vehicle.
    • The ticket records information like the entry time, the assigned ParkingSpot, and the vehicle’s license plate.
  • 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.

Ticket and Payment

  • Relationship: Association
    • A Payment is associated with a Ticket.
    • The Payment records the details of the transaction, such as the amount paid and the payment time.
  • Interaction:
    • When the vehicle exits, the system calculates the fee using the Ticket and processes a Payment for it.
    • The Payment marks the Ticket as settled, completing the parking session.

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.
  • 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()
  • 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.

2. Vehicle

  • Base Class: Vehicle
    • Attributes: licensePlate, type
  • Subclasses:
    • Car
      • Default for regular passenger cars.
    • Bike
      • For two-wheeled vehicles.
    • Truck
      • For large cargo or heavy-duty vehicles.

3. User

  • Base Class: User
    • Attributes: userId, name, role
    • Methods: parkVehicle(Vehicle v), retrieveVehicle(String licensePlate)
  • Subclasses:
    • Admin
      • Additional responsibilities:
        • Configure parking lot (add/remove floors, spots).
        • Update pricing policies.
    • Attendant
      • Additional responsibilities:
        • Assist users with parking and retrieval.
        • Update parking spot statuses.
    • RegularUser
      • Responsibilities:
        • Park and retrieve vehicles.
        • View parking history.

4. Ticket and Payment

  • Ticket
    • A standalone class with attributes:
      • ticketId, vehicle, parkingSpot, entryTime, exitTime, fee
    • Methods: calculateFee()
  • Payment
    • A standalone class with attributes:
      • paymentId, amount, paymentTime, ticket
    • Methods: processPayment(Ticket ticket, double amount)






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.
  • Improvement:
    • If the system grows, consider separating Fee Calculation logic into a dedicated service (e.g., FeeCalculator).


2. Open/Closed Principle (OCP)

Definition: A class should be open for extension but closed for modification.

Analysis:

  • Adheres:
    • The ParkingSpot and Vehicle hierarchies allow adding new types (e.g., MotorcycleSpot, Bus) without modifying existing classes.
    • The fee calculation logic in Ticket can be extended using a FeeCalculator strategy without modifying the Ticket class itself.
  • Improvement:
    • Introduce the Strategy Pattern for fee calculation. This would allow adding new pricing models (e.g., discounted rates) without modifying the Ticket class.


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 the ParkingSpot base class wherever it is used.
    • The Vehicle subclasses (Car, Bike, Truck) adhere to the same interface and can be substituted without issues.


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 for ParkingSpot or processPayment for Payment).


5. Dependency Inversion Principle (DIP)

Definition: High-level modules should not depend on low-level modules but on abstractions.

Analysis:

  • Adheres:
    • The 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.
  • Improvement:
    • Currently, the fee calculation logic is directly implemented in Ticket. Using a FeeCalculator abstraction would better adhere to DIP by decoupling fee calculation from the Ticket class.