Home/Blog/Programming/Design a parking lot
Home/Blog/Programming/Design a parking lot

Design a parking lot

Saif Ali
13 min read

Mastering the art of designing scalable and efficient systems is paramount in object-oriented design (OOD) interviews. One common scenario presented to software engineers during these interviews is designing a parking lot system. This seemingly straightforward problem encompasses various design principles and considerations, making it an excellent litmus test for a candidate’s OOD skills.

This blog delves into the intricacies of designing a parking lot system using object-oriented principles, patterns, and best practices. We’ll leverage a comprehensive implementation that covers various parking lot system components, including vehicle management, parking spot allocation, ticketing, payment processing, etc. Additionally, we’ll explore an OOD interview guide, providing insights and guidelines for effectively navigating low-level design interviews.

Problem definition#

A parking lot system is an essential infrastructure in various public venues like shopping malls, stadiums, and offices, providing designated vehicle spaces. These lots contain a fixed number of parking spots tailored for different vehicle types, each charged based on the parking duration. Upon entry, vehicles receive a parking ticket to track their stay. When exiting, drivers can settle their fees at automated exit panels, utilizing payment methods such as cards or cash. This system ensures efficient vehicle management and revenue collection within parking facilities.

The layout of the parking lot
The layout of the parking lot

Requirements collection#

Let’s define the requirements for the parking lot problem:

  • R1: Four vehicles should be allowed to park in the parking lot: Car, Truck, Van, and Motorcycle.

  • R2: The parking lot should have three spots: CompactSpot, LargeSpot, and MotorcycleSpot.

  • R3: The parking lot should have a display board that shows its status, such as the number of spots, free spots, and occupied spots.

  • R4: If the parking lot is completely occupied, the system should display a message on the entrance and the parking lot display board.

  • R5: The system should generate a receipt at the exit.

  • R6: Customers should be able to collect a parking ticket from the entrance and pay at the exit.

  • R7: Payment should be calculated hourly and based on vehicle type.

  • R8: Payment can be made using either a credit/debit card or cash.

Class diagram#

In the class diagram for the parking lot system, we’ll identify and design classes, abstract classes, and interfaces based on the requirements.

Components of a parking lot system#

As we take a bottom-up approach to designing the parking lot system, we’ll begin by identifying and designing the classes for smaller components like vehicles and parking spots. Subsequently, we’ll integrate these smaller components into the class representing the parking lot system.

The Vehicle class#

The Vehicle class serves as the base class for a generic vehicle, with attributes for licensePlate and type, along with corresponding getters and setters. Subclasses Motorcycle, Car, Van, and Truck extend the Vehicle class, each initializing the licensePlate attribute and the vehicle type.

The class diagram of Vehicle and its child classes
The class diagram of Vehicle and its child classes

The ParkingSpot class#

The ParkingSpot class is the base class, featuring attributes such as vehicle, type, and occupied. Subclasses CompactSpot, MotorcycleSpot, and LargeSpot inherit from ParkingSpot, representing specific types of parking spots tailored for different vehicle sizes or types. Each subclass inherits the base class’s attributes and behaviors, enabling the representation of various parking spot types within the system.

The class diagram of ParkingSpot and its child classes
The class diagram of ParkingSpot and its child classes

The PaymentStrategy interface#

The PaymentStrategy interface handles different payment methods and provides concrete implementations for CardPayment and CashPayment. Each concrete class implements the pay method according to its respective payment method. This design allows for easy addition of new payment methods in the future without modifying existing code.

The class diagram of PaymentStrategy and its child classes
The class diagram of PaymentStrategy and its child classes

The VehicleFactory class#

The VehicleFactory class simplifies creating instances of different vehicle subclasses by encapsulating the creation logic within a single class, promoting code organization and maintainability.

The class diagram of the VehicleFactory class
The class diagram of the VehicleFactory class

The ParkingSpotFactory class#

The ParkingSpotFactory class simplifies creating instances of different parking spot subclasses by encapsulating the creation logic within a single class, promoting code organization and maintainability.

The class diagram of the ParkingSpotFactory class
The class diagram of the ParkingSpotFactory class

The CentralPaymentSystem class#

The CentralPaymentSystem class provides a centralized mechanism for processing payments in a system, ensuring consistency and encapsulating payment-related functionalities within a single component.

The class diagram of the CentralPaymentSystem class
The class diagram of the CentralPaymentSystem class

The ParkingFloor class#

The ParkingFloor class encapsulates the functionality of managing parking spots on a particular parking lot floor. It provides methods for adding, removing, and retrieving information about parking spots on the floor.

The class diagram of the ParkingFloor class
The class diagram of the ParkingFloor class

The ParkingTicket class#

The ParkingTicket class encapsulates the data and behavior associated with a parking ticket, including its unique identifier, associated vehicle, and entry and exit times.

The class diagram of the ParkingTicket class
The class diagram of the ParkingTicket class

The FareCalculator class#

The FareCalculator class encapsulates the logic for calculating parking fares based on the vehicle type and the parking duration. It provides flexibility to customize rates for different vehicle types and ensures accurate fare calculation.

 The class diagram of the FareCalculator class
The class diagram of the FareCalculator class

The EntrancePanel class#

The EntrancePanel class encapsulates the logic for issuing parking tickets to vehicles entering the parking lot, ensuring that vehicles are parked in suitable spots, and maintaining records of issued tickets.

The class diagram of the EntrancePanel class
The class diagram of the EntrancePanel class

The Receipt class#

The Receipt class provides a convenient way to display the receipt details for a parking transaction using the provided ParkingTicket object and fare information.

The class diagram of the Receipt class
The class diagram of the Receipt class

The ExitPanel class#

The ExitPanel class encapsulates the logic for handling vehicle exit operations, including calculating fares, processing payments, and updating parking lot records.

The class diagram of the ExitPanel class
The class diagram of the ExitPanel class

The DisplayBoard class#

The DisplayBoard class provides a convenient way to visualize a parking lot’s status by encapsulating the logic for retrieving and displaying relevant information from the associated ParkingLot object.

The class diagram of the DisplayBoard class
The class diagram of the DisplayBoard class

The ParkingLot class#

The ParkingLot class encapsulates the functionality of managing parking spots, parking tickets, and vehicle presence in the parking lot system. It provides methods for accessing and manipulating parking lot data, enabling efficient parking lot management operations.

The class diagram of the ParkingLot class
The class diagram of the ParkingLot class

The ParkingLotInitializer class #

Using predefined spot types, the ParkingLotInitializer class encapsulates the logic for initializing a parking lot with a specified structure, including the number of floors and spots per floor.

The class diagram of the ParkingLotInitializer class
The class diagram of the ParkingLotInitializer class

The ParkingSpotManager class#

The ParkingSpotManager class encapsulates the logic for parking and removing vehicles from parking spots, finding spots for specific vehicles, and freeing up parking spots when needed within a parking lot system.

The class diagram of the ParkingSpotManager class
The class diagram of the ParkingSpotManager class

Relationship between the classes#

The relationship between the classes in the parking lot system implementation can be summarized as follows:

Association#

The class diagram exhibits the following association relationships:

One-way association#
  • The VehicleFactory has a one-way association with Vehicle.

  • The ParkingSpotFactory has a one-way association with ParkingSpot.

  • The ParkingSpot has a one-way association with Vehicle.

  • The ParkingSpotManager has a one-way association with Vehicle and ParkingSpot.

  • The DisplayBoard has a one-way association with ParkingLot.

  • The EntrancePanel has a one-way association with ParkingSpotManager and ParkingLot.

  • The ExitPanel has a one-way association with ParkingSpotManager, Receipt, CentralPaymentSystem, and ParkingLot.

  • The CentralPaymentSystem has a one-way association with PaymentStrategy.

  • The FairCalculator has a one-way association with ParkingTicket.

The one-way association relationship between classes
The one-way association relationship between classes
Two-way association#
  • The ParkingSpotManager has a two-way association with ParkingLot.

The two-way association relationship between classes
The two-way association relationship between classes

Aggregation#

The class diagram has the following aggregation relationships:

  • The ParingFloor has an aggregation relationship with ParkingSpot.

  • The ParkingLot has an aggregation relationship with ParkingTicket and ParkingFloor.

  • The ParkingLotInitializer has an aggregation relationship with ParkingLot.

The aggregation relationship between classes
The aggregation relationship between classes

Inheritance#

The following classes show an inheritance relationship:

  • The Car, Truck, Van, and Motorcycle classes inherit from the Vehicle class.

  • The CompactSpot, LargeSpot, and MotorcycleSpot classes inherit from the ParkingSpot class.

  • The CashPayment and CardPayment classes implement the PaymentStrategy interface.

Note: We have already discussed the inheritance relationship between classes in the class diagram section above.

Class diagram of the parking lot system#

Here is the complete class diagram for our parking lot system:

The class diagram of the parking lot system
The class diagram of the parking lot system

Design patterns#

In the implementation of the parking lot system, several design patterns are utilized to enhance the structure, maintainability, and flexibility of the system:

Design patterns

Description

Singleton pattern

The CentralPaymentSystem class employs the Singleton pattern to ensure that only one instance of the payment system exists throughout the application. This guarantees centralized control over payment transactions, facilitating better management and coordination.

Factory method pattern

The Factory Method pattern is implemented in the VehicleFactory and ParkingSpotFactory classes to create instances of various vehicle and parking spot subclasses, respectively. By encapsulating the object creation process, these factories allow the client code to create objects without knowing the specific subclass implementations, promoting code flexibility and scalability.

Strategy pattern

The Strategy pattern is employed in the PaymentStrategy interface and its concrete implementations (CardPayment, CashPayment). This pattern encapsulates different payment strategies into separate classes, allowing clients to dynamically choose a strategy at runtime. Decoupling the payment logic from the client code enables easier integration of new payment methods and promotes code reusability and maintainability.

Observer pattern

The Observer pattern is utilized in the DisplayBoard class, where it observes changes in the parking lot status and updates the display accordingly. By decoupling the display functionality from the parking lot logic, this pattern ensures that the display is automatically updated whenever there are changes in the parking lot status, such as vehicles entering or exiting. This enhances the system’s modularity and extensibility.

Command pattern

The Command pattern is implemented in the handler classes (EnterVehicleHandler, ExitVehicleHandler, DisplayCurrentVehiclesHandler, MenuOptionHandler), where each handler encapsulates a specific request as an object. This allows clients to parameterize objects with requests, facilitating queueing, logging, and support for undoable operations. By separating the request from its execution, this pattern enhances the flexibility and maintainability of the system architecture.

SOLID design principles#

The SOLID principles aim to make software designs more understandable, flexible, and maintainable. They consist of:

Design Principle

Description

Single responsibility principle (SRP)

The Vehicle class in the parking lot system adheres to the SRP by encapsulating a vehicle’s license plate and type information. It focuses solely on representing a generic vehicle without being concerned with parking or payment functionalities. Similarly, the ParkingTicket class handles the responsibility of storing ticket information such as ticket number, vehicle, entry time, and exit time without being involved in payment processing or parking spot management.

Open/Closed principle (OCP)

The ParkingSpotFactory class exemplifies the OCP by allowing the system to be extended with new types of parking spots without modifying existing code. Using the factory pattern, new subclasses of ParkingSpot can be created to represent different types of parking spots (e.g., handicapped spots) without altering the ParkingLotInitializer or other parts of the system.

Liskov substitution principle (LSP)

The Vehicle hierarchy demonstrates adherence to the LSP by enabling subclasses like Car, Van, Truck, and Motorcycle to be seamlessly substituted for their parent class, Vehicle. This principle ensures that objects of the Vehicle superclass can be replaced with objects of its subclasses without affecting the correctness of the program. For example, any method that accepts a Vehicle object can also work correctly with instances of its subclasses, such as a Car or Truck.

Interface segregation principle (ISP)

The PaymentStrategy interface embodies the ISP by defining a single method of pay(double amount), which encapsulates the behavior of different payment strategies. Concrete implementations like CardPayment and CashPayment must only implement this single method, ensuring that clients are not forced to depend on methods they do not use. This principle promotes the creation of cohesive interfaces that are tailored to specific client requirements.

Dependency inversion principle (DIP)

The ParkingLot class adheres to the DIP by depending on abstractions rather than concrete implementations. For instance, it relies on the ParkingSpotManager interface for parking spot management operations, allowing different implementations of spot management strategies to be used interchangeably. This promotes loose coupling between classes and facilitates easier testing, maintenance, and extension of the system.

Code for the parking lot system#

We’ve gone over the different aspects of the parking lot system and observed the attributes attached to the problem using the class diagram. Let’s explore the more practical side of things, where we will work on implementing the parking lot system using multiple languages. This is usually the last step in an object-oriented design interview process.

We have selected the Java programming languages to implement the parking lot system.

The Vehicle hierarchy#

The Vehicle hierarchy includes Car, Motorcycle, Van, and Truck, all inherited from a base Vehicle class.

class Vehicle {
private String licensePlate;
private String type;
public Vehicle(String licensePlate, String type) {
this.licensePlate = licensePlate;
this.type = type;
}
// Getters and setters
public String getLicensePlate() {
return licensePlate;
}
public void setLicensePlate(String licensePlate) {
this.licensePlate = licensePlate;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
}
class Motorcycle extends Vehicle {
public Motorcycle(String licensePlate) {
super(licensePlate, "Motorcycle");
}
}
class Car extends Vehicle {
public Car(String licensePlate) {
super(licensePlate, "Car");
}
}
class Van extends Vehicle {
public Van(String licensePlate) {
super(licensePlate, "Van");
}
}
class Truck extends Vehicle {
public Truck(String licensePlate) {
super(licensePlate, "Truck");
}
}
// VehicleFactory to create instances of Vehicle subclasses
class VehicleFactory {
public Vehicle createVehicle(String type, String licensePlate) {
// Convert type to uppercase
type = type.toUpperCase();
switch (type) {
case "VAN":
return new Van(licensePlate);
case "TRUCK":
return new Truck(licensePlate);
case "CAR":
return new Car(licensePlate);
case "MOTORCYCLE":
return new Motorcycle(licensePlate);
default:
throw new IllegalArgumentException("Invalid vehicle type: " + type);
}
}
}

Parking spot management#

A ParkingSpotManager class manages different types of parking spots (Compact, Large, Motorcycle). This class handles parking and removing vehicles from spots.

class ParkingSpot {
private Vehicle vehicle;
private String type;
private boolean occupied;
public ParkingSpot() {
this.occupied = false;
}
// Getters and setters
public Vehicle getVehicle() {
return vehicle;
}
public void setVehicle(Vehicle vehicle) {
this.vehicle = vehicle;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public void setOccupied(boolean occupied) {
this.occupied = occupied;
}
public boolean getOccupied() {
return occupied;
}
}
class MotorcycleSpot extends ParkingSpot {
public MotorcycleSpot() {
super();
}
}
class CompactSpot extends ParkingSpot {
public CompactSpot() {
super();
}
}
class LargeSpot extends ParkingSpot {
public LargeSpot() {
super();
}
}
// ParkingSpotFactory to create instances of ParkingSpot subclasses
class ParkingSpotFactory {
public ParkingSpot createParkingSpot(String type) {
switch (type) {
case "Compact":
return new CompactSpot();
case "Large":
return new LargeSpot();
case "Motorcycle":
return new MotorcycleSpot();
default:
throw new IllegalArgumentException("Invalid parking spot type: " + type);
}
}
}
// ParkingSpotManager class to manage parking spot operations
class ParkingSpotManager {
public boolean isOccupied(ParkingSpot spot) {
return spot.getVehicle() != null;
}
// Member function to park a vehicle in a parking spot
public void parkVehicle(ParkingSpot spot, Vehicle vehicle, String type) {
spot.setVehicle(vehicle);
spot.setType(type);
}
// Member function to remove a vehicle from a parking spot
public void removeVehicle(ParkingSpot spot) {
spot.setVehicle(null);
spot.setType(""); // Reset type
}
// Member function to get a parking spot for a specific vehicle
public ParkingSpot getSpotForVehicle(ParkingLot parkingLot, Vehicle vehicle) {
for (ParkingFloor floor : parkingLot.getFloors()) {
for (ParkingSpot spot : floor.getSpots()) {
if (isOccupied(spot) && spot.getVehicle().equals(vehicle)) {
return spot;
}
}
}
return null; // Return null if no spot found for the vehicle
}
// Member function to free up a parking spot
public void freeParkingSpot(ParkingSpot spot) {
spot.setVehicle(null);
spot.setOccupied(false);
}
}

Payment system#

Payment strategies (CardPayment, CashPayment) are implemented using the PaymentStrategy interface. The CentralPaymentSystem class handles payment processing.

// PaymentStrategy interface for different payment methods
interface PaymentStrategy {
void pay(double amount);
}
class CashPayment implements PaymentStrategy {
public void pay(double amount) {
// Implementation for cash payment
}
}
class CardPayment implements PaymentStrategy {
public void pay(double amount) {
// Implementation for card payment
}
}
// CentralPaymentSystem to handle payments
class CentralPaymentSystem {
private static CentralPaymentSystem instance;
private CentralPaymentSystem() {
// Private constructor to prevent instantiation
}
public static CentralPaymentSystem getInstance() {
if (instance == null) {
instance = new CentralPaymentSystem();
}
return instance;
}
// Methods for payment processing
public void processPayment(PaymentStrategy paymentStrategy, double amount) {
paymentStrategy.pay(amount);
}
}

Parking floor#

Represents a level or floor within the parking lot containing a collection of parking spots. It manages the addition and removal of parking spots.

// ParkingFloor class representing a floor in the parking lot
class ParkingFloor {
private List<ParkingSpot> spots;
// Constructors
public ParkingFloor() {
this.spots = new ArrayList<>();
}
// Getters and setters
public List<ParkingSpot> getSpots() {
return spots;
}
public void setSpots(List<ParkingSpot> spots) {
this.spots = spots;
}
// Member function
public void addParkingSpot(ParkingSpot spot) {
spots.add(spot);
}
public void removeParkingSpot(ParkingSpot spot) {
spots.remove(spot);
}
public int getNumberOfSpots() {
return spots.size();
}
}

Fare calculator#

Calculates parking fare based on vehicle type and duration of parking stay, with customizable hourly rates for different vehicle types.

// FareCalculator class for calculating fare
class FareCalculator {
private double carRatePerHour;
private double vanRatePerHour;
private double truckRatePerHour;
private double motorcycleRatePerHour;
// Default constructor with default values
public FareCalculator() {
this.carRatePerHour = 10.0; // Default rate for cars
this.vanRatePerHour = 15.0; // Default rate for vans
this.truckRatePerHour = 20.0; // Default rate for trucks
this.motorcycleRatePerHour = 5.0; // Default rate for motorcycles
}
// Constructor with custom rates
public FareCalculator(double carRatePerHour, double vanRatePerHour, double truckRatePerHour, double motorcycleRatePerHour) {
this.carRatePerHour = carRatePerHour;
this.vanRatePerHour = vanRatePerHour;
this.truckRatePerHour = truckRatePerHour;
this.motorcycleRatePerHour = motorcycleRatePerHour;
}
// Getters and setters
public double getCarRatePerHour() {
return carRatePerHour;
}
public void setCarRatePerHour(double carRatePerHour) {
this.carRatePerHour = carRatePerHour;
}
public double getVanRatePerHour() {
return vanRatePerHour;
}
public void setVanRatePerHour(double vanRatePerHour) {
this.vanRatePerHour = vanRatePerHour;
}
public double getTruckRatePerHour() {
return truckRatePerHour;
}
public void setTruckRatePerHour(double truckRatePerHour) {
this.truckRatePerHour = truckRatePerHour;
}
public double getMotorcycleRatePerHour() {
return motorcycleRatePerHour;
}
public void setMotorcycleRatePerHour(double motorcycleRatePerHour) {
this.motorcycleRatePerHour = motorcycleRatePerHour;
}
// Member function to calculate fare
public double calculateFare(ParkingTicket ticket) {
LocalDateTime entryTime = ticket.getEntryTime();
LocalDateTime exitTime = ticket.getExitTime();
long hoursParked = ChronoUnit.HOURS.between(entryTime, exitTime);
// Adding 1 hour to the parked duration to ensure at least one hour is charged for payment
if (hoursParked < 1)
hoursParked=1;
double ratePerHour;
switch (ticket.getVehicle().getType()) {
case "Car":
ratePerHour = carRatePerHour;
break;
case "Van":
ratePerHour = vanRatePerHour;
break;
case "Truck":
ratePerHour = truckRatePerHour;
break;
case "Motorcycle":
ratePerHour = motorcycleRatePerHour;
break;
default:
throw new IllegalArgumentException("Invalid vehicle type");
}
return hoursParked * ratePerHour;
}
}

Ticket management#

The ParkingTicket class manages ticket information, including entry and exit times, vehicle details, and fare calculation.

class ParkingTicket {
private String ticketNumber;
private Vehicle vehicle;
private LocalDateTime entryTime;
private LocalDateTime exitTime;
// Constructors
public ParkingTicket(String ticketNumber, Vehicle vehicle, LocalDateTime entryTime) {
this.ticketNumber = ticketNumber;
this.vehicle = vehicle;
this.entryTime = entryTime;
}
// Getters and setters
public String getTicketNumber() {
return ticketNumber;
}
public void setTicketNumber(String ticketNumber) {
this.ticketNumber = ticketNumber;
}
public Vehicle getVehicle() {
return vehicle;
}
public void setVehicle(Vehicle vehicle) {
this.vehicle = vehicle;
}
public LocalDateTime getEntryTime() {
return entryTime;
}
public void setEntryTime(LocalDateTime entryTime) {
this.entryTime = entryTime;
}
public LocalDateTime getExitTime() {
return exitTime;
}
public void setExitTime(LocalDateTime exitTime) {
this.exitTime = exitTime;
}
}

Entrance and exit panels#

Both EntrancePanel and ExitPanel classes handle vehicle entry and exit operations, respectively.

// EntrancePanel class for vehicle entrance
class EntrancePanel {
private ParkingLot parkingLot;
private ParkingSpotManager spotManager;
// Constructors
public EntrancePanel(ParkingLot parkingLot, ParkingSpotManager spotManager) {
this.parkingLot = parkingLot;
this.spotManager = spotManager;
}
// Member function
public ParkingTicket issueParkingTicket(Vehicle vehicle) {
// Check if spotManager is not null before accessing its methods
// Check if there are available spots in the parking lot
// Generate ticket details and return the issued parking ticket
// Return the issued parking ticket
}
// Helper method to check if the spot is compatible with the vehicle
private boolean isCompatibleSpot(Vehicle vehicle, ParkingSpot spot) {
if (spot instanceof MotorcycleSpot) {
return vehicle instanceof Motorcycle;
} else if (spot instanceof CompactSpot) {
return vehicle instanceof Car;
} else if (spot instanceof LargeSpot) {
return true; // Large spots allow any type of vehicle
}
return false;
}
}
// ExitPanel class for vehicle exit
class ExitPanel {
private ParkingLot parkingLot;
private ParkingSpotManager spotManager;
// Constructors
public ExitPanel(ParkingLot parkingLot, ParkingSpotManager spotManager) {
this.parkingLot = parkingLot;
this.spotManager = spotManager;
}
// Getters and setters
public ParkingLot getParkingLot() {
return parkingLot;
}
public void setParkingLot(ParkingLot parkingLot) {
this.parkingLot = parkingLot;
}
// Member function to process vehicle exit
public void processExit(String licensePlate) {
// Check if spotManager is not null before accessing its methods
// Find the ticket associated with the provided license plate
// Set exit time
// Calculate the total hours parked
// Calculate the fare using fare calculator
// Generate the receipt
// Free up the parking spot
// Remove the ticket from the parking lot
// Prompt user for payment method
Scanner scanner = new Scanner(System.in);
System.out.println("\nChoose an option for payment:");
System.out.println("1. Cash");
System.out.println("2. Card");
System.out.println("3. Exit program");
int choice = scanner.nextInt();
scanner.nextLine(); // Consume newline
switch (choice) {
case 1:
PaymentStrategy cashPayment = new CashPayment();
CentralPaymentSystem.getInstance().processPayment(cashPayment, fare);
break;
case 2:
PaymentStrategy cardPayment = new CardPayment();
CentralPaymentSystem.getInstance().processPayment(cardPayment, fare);
break;
case 3:
System.out.println("Exiting program...");
scanner.close();
return;
default:
System.out.println("Invalid choice of payment. Please try again.");
}
}
// Helper function to find the parking ticket associated with the provided license plate
private ParkingTicket findTicketByLicensePlate(String licensePlate) {
for (ParkingTicket ticket : parkingLot.getTicketMap().values()) {
if (ticket.getVehicle().getLicensePlate().equals(licensePlate)) {
return ticket;
}
}
return null; // Return null if no ticket found for the provided license plate
}
}

Receipt generation#

The Receipt class generates receipts displaying ticket information, entry and exit times, total hours parked, and fare.

public class Receipt {
public static void displayReceipt(ParkingTicket ticket, double fare, long totalHoursParked) {
System.out.println("\nReceipt:");
System.out.println("Ticket Number: " + ticket.getTicketNumber());
System.out.println("Vehicle Type: " + ticket.getVehicle().getType());
System.out.println("Entry Time: " + ticket.getEntryTime().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
System.out.println("Exit Time: " + ticket.getExitTime().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
System.out.println("Total Hours Parked: " + totalHoursParked);
System.out.println("Fare: $" + fare);
}
}

Display board#

The DisplayBoard class displays parking lot status, including total spots, free spots, occupied spots, and current vehicles.

class DisplayBoard {
private ParkingLot parkingLot;
// Constructors
public DisplayBoard(ParkingLot parkingLot) {
this.parkingLot = parkingLot;
}
// Getters and setters
public ParkingLot getParkingLot() {
return parkingLot;
}
public void setParkingLot(ParkingLot parkingLot) {
this.parkingLot = parkingLot;
}
// Member function
public void displayParkingLotStatus() {
System.out.println("\nParking Lot Status:");
System.out.println("Total Spots: " + parkingLot.getTotalSpots());
System.out.println("Free Spots: " + parkingLot.getTotalFreeSpots());
System.out.println("Occupied Spots: " + parkingLot.getOccupiedSpots());
System.out.println("Current Vehicles:");
List<Vehicle> vehicles = parkingLot.getCurrentVehicles();
for (Vehicle vehicle : vehicles) {
System.out.println("License Plate: " + vehicle.getLicensePlate() + ", Type: " + vehicle.getType());
}
}
}

Parking lot#

The central entity manages the parking system, including floors, spots, tickets, and current vehicle status. It initializes, tracks, and updates parking lot information.

class ParkingLot {
private List<ParkingFloor> floors;
private Map<String, ParkingTicket> ticketMap;
private ParkingSpotManager spotManager;
// Constructors
public ParkingLot() {
this.floors = new ArrayList<>();
this.ticketMap = new HashMap<>();
this.spotManager = new ParkingSpotManager();
}
// Getters and setters
public List<ParkingFloor> getFloors() {
return floors;
}
public void setFloors(List<ParkingFloor> floors) {
this.floors = floors;
}
public Map<String, ParkingTicket> getTicketMap() {
return ticketMap;
}
public void setTicketMap(Map<String, ParkingTicket> ticketMap) {
this.ticketMap = ticketMap;
}
public ParkingTicket getTicket(String ticketNumber) {
return ticketMap.get(ticketNumber);
}
// Method to get all tickets
public Map<String, ParkingTicket> getTickets() {
return ticketMap;
}
// Updated method to get total free spots
public int getTotalFreeSpots() {
// definition
}
// Updated method to get total free spots by type
public int getTotalFreeSpotsByVehicleType(String vehicleType) {
// definition
}
public List<Vehicle> getCurrentVehicles() {
// definition
}
// Additional methods for managing parking tickets
public void addTicket(String ticketNumber, ParkingTicket ticket) {
ticketMap.put(ticketNumber, ticket);
}
public void removeTicket(String ticketNumber) {
ticketMap.remove(ticketNumber);
}
// Updated method to get the parking spot for a given vehicle
public ParkingSpot getSpotForVehicle(Vehicle vehicle) {
// definition
}
public int getTotalSpots() {
int totalSpots = 0;
for (ParkingFloor floor : floors) {
totalSpots += floor.getSpots().size();
}
return totalSpots;
}
// Updated method to get the total occupied spots
public int getOccupiedSpots() {
int occupiedSpots = 0;
for (ParkingFloor floor : floors) {
for (ParkingSpot spot : floor.getSpots()) {
if (spotManager.isOccupied(spot)) {
occupiedSpots++;
}
}
}
return occupiedSpots;
}
public boolean isLicensePlatePresent(String licensePlate) {
for (ParkingTicket ticket : ticketMap.values()) {
if (ticket.getVehicle().getLicensePlate().equals(licensePlate)) {
return true; // License plate already present
}
}
return false; // License plate not found
}
}

Parking lot initialization#

The ParkingLotInitializer class initializes the parking lot with a specified number of floors and spots per floor.

// ParkingLotInitializer class for initializing parking lot
class ParkingLotInitializer {
private ParkingLot parkingLot;
private ParkingSpotFactory spotFactory;
// Constructors
public ParkingLotInitializer(ParkingLot parkingLot) {
this.parkingLot = parkingLot;
this.spotFactory = new ParkingSpotFactory();
}
// Getters and setters
public ParkingLot getParkingLot() {
return parkingLot;
}
public void setParkingLot(ParkingLot parkingLot) {
this.parkingLot = parkingLot;
}
// Member function
public void initializeParkingLot(int numberOfFloors, int spotsPerFloor) {
List<ParkingFloor> floors = new ArrayList<>();
List<String> spotTypes = List.of("Compact", "Large", "Motorcycle");
for (int i = 0; i < numberOfFloors; i++) {
ParkingFloor floor = new ParkingFloor();
for (int j = 0; j < spotsPerFloor; j++) {
String spotType = spotTypes.get(j % spotTypes.size()); // Alternate spot types
ParkingSpot spot = spotFactory.createParkingSpot(spotType);
floor.addParkingSpot(spot);
}
floors.add(floor);
}
parkingLot.setFloors(floors);
}
}

User interaction#

The system provides options for users to enter vehicles, exit vehicles, display current vehicles, and view parking lot status. User inputs are managed through various handlers like EnterVehicleHandler, ExitVehicleHandler, DisplayCurrentVehiclesHandler, and MenuOptionHandler, and these classes are not part of the core design of the parking lot system.

class EnterVehicleHandler {
private Scanner scanner;
private ParkingLot parkingLot;
private ParkingSpotManager spotManager;
public EnterVehicleHandler(Scanner scanner, ParkingLot parkingLot, ParkingSpotManager spotManager) {
this.scanner = scanner;
this.parkingLot = parkingLot;
this.spotManager = spotManager;
}
public void execute() {
// Implementation to enter a vehicle
Scanner scanner = new Scanner(System.in);
String vehicleType;
String licensePlate;
// Prompt for vehicle type until a non-empty and valid input is provided
do {
System.out.println("Enter vehicle type (Car, Van, Truck, Motorcycle):");
vehicleType = scanner.nextLine().trim().toLowerCase(); // Trim leading and trailing whitespace and convert to lowercase
if (vehicleType.isEmpty()) {
System.out.println("Vehicle type cannot be empty. Please enter a valid vehicle type.");
} else if (!isValidVehicleType(vehicleType)) {
System.out.println("Invalid vehicle type. Please enter a valid vehicle type (Car, Van, Truck, Motorcycle).");
}
} while (vehicleType.isEmpty() || !isValidVehicleType(vehicleType));
// Prompt for license plate until a non-empty input is provided
do {
System.out.println("Enter license plate:");
licensePlate = scanner.nextLine().trim(); // Trim leading and trailing whitespace
if (licensePlate.isEmpty()) {
System.out.println("License plate cannot be empty. Please enter a valid license plate.");
}
} while (licensePlate.isEmpty());
// Check if the license plate is already present in the parking lot
if (parkingLot.isLicensePlatePresent(licensePlate)) {
System.out.println("Vehicle with the provided license plate is already parked. Please enter a different license plate.");
return; // Exit the function
}
VehicleFactory vehicleFactory = new VehicleFactory();
Vehicle vehicle = vehicleFactory.createVehicle(vehicleType, licensePlate);
EntrancePanel entrancePanel = new EntrancePanel(parkingLot, spotManager);
entrancePanel.issueParkingTicket(vehicle);
}
private boolean isValidVehicleType(String vehicleType) {
return vehicleType.equals("car") || vehicleType.equals("van") || vehicleType.equals("truck") || vehicleType.equals("motorcycle");
}
}
class ExitVehicleHandler {
private Scanner scanner;
private ParkingLot parkingLot;
private ParkingSpotManager spotManager;
public ExitVehicleHandler(Scanner scanner, ParkingLot parkingLot, ParkingSpotManager spotManager) {
this.scanner = scanner;
this.parkingLot = parkingLot;
this.spotManager = spotManager;
}
public void execute() {
// Implementation to exit a vehicle
Scanner scanner = new Scanner(System.in);
System.out.println("Enter license plate number:");
String licensePlate = scanner.nextLine();
ExitPanel exitPanel = new ExitPanel(parkingLot, spotManager);
exitPanel.processExit(licensePlate);
}
}
class DisplayCurrentVehiclesHandler {
private ParkingLot parkingLot;
public DisplayCurrentVehiclesHandler(ParkingLot parkingLot) {
this.parkingLot = parkingLot;
}
public void execute() {
// Implementation to display current vehicles
DisplayBoard displayBoard = new DisplayBoard(parkingLot);
List<Vehicle> currentVehicles = displayBoard.getParkingLot().getCurrentVehicles();
for (Vehicle vehicle : currentVehicles) {
System.out.println("Vehicle Type: " + vehicle.getType() + ", License Plate: " + vehicle.getLicensePlate());
}
}
}

Main execution #

The DriverMain class initializes the parking lot, manages user interactions through a menu system, and orchestrates the overall flow of the parking lot system.

public class DriverMain {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
ParkingLot parkingLot = new ParkingLot();
ParkingLotInitializer parkingLotInitializer = new ParkingLotInitializer(parkingLot);
parkingLotInitializer.initializeParkingLot(3, 30);
ParkingSpotManager spotManager = new ParkingSpotManager();
DisplayBoard displayBoard = new DisplayBoard(parkingLot);
displayBoard.displayParkingLotStatus();
MenuOptionHandler menuOptionHandler = new MenuOptionHandler(scanner, parkingLot, spotManager, displayBoard);
menuOptionHandler.runMenu();
}
}

The above-implemented parking lot system offers comprehensive functionalities for vehicle entry and exit, payment processing, and real-time status display. Employing modular design patterns like Strategy and Factory ensures flexibility to adapt to evolving requirements. This design establishes a robust foundation for a scalable and adaptable parking management solution, allowing customization and enhancement. You can modify and improve the design according to your needs and preferences.

OOD interview guide#

Object-oriented design (OOD) interviews test your ability to solve complex problems using OOD principles and design patterns. Here’s a simple guide to help you prepare:

  • Understand OOD concepts: Learn the key principles of OOD, like abstraction, encapsulation, inheritance, and polymorphism for scalable and maintainable systems.

  • Practice solving problems: Regularly solve coding problems that require object-oriented solutions. Practice designing object models for real-world scenarios, like a parking lot or a banking system.

  • Know design patterns: Familiarize yourself with design patterns like Singleton, Factory, Strategy, Observer, and State, and learn how and when to use them.

  • System design skills: Focus on designing systems that are scalable, perform well, and can be easily extended with new features. Be prepared to discuss why you made certain design choices based on constraints.

  • Effective communication: Clearly explain your problem-solving process and design decisions. Be open to suggestions and able to discuss alternative solutions.

  • Review past projects: Reflect on previous projects and identify patterns and challenges you encountered. Be ready to discuss these projects with interviewers, explaining how you applied OOD principles.

  • Stay updated: Stay informed about the latest OOD and software design trends.

  • Mock interviews: Participate in realistic mock interviews to simulate the interview experience. Use feedback from these mock interviews to improve your skills. You can visit the Educative AI-based mock interview to prepare for FAANG/MAANG object-oriented design, system design, API design, and coding interviews.

  • Continuous learning: Engage with the software development community by joining forums, attending meetups, and participating in discussions. Continuously practice your skills to stay sharp and improve over time.

By following this guide and dedicating time to practice, you can build confidence to do well in OOD interviews and handle tough design problems effectively.

Conclusion#

Mastering object-oriented design principles is essential for tackling complex system design challenges like designing a parking lot. We can create scalable and maintainable solutions by breaking down the problem, applying SOLID principles, and utilizing design patterns effectively. Remember to practice problem-solving, understand design patterns, prioritize code readability, hone system design skills, and foster effective communication to excel in interviews and real-world projects. Keep learning and practicing to become a proficient and versatile developer.

Next steps#

If you’re looking to broaden your understanding and delve deeper into problem-solving, object-oriented design, design patterns, and SOLID principles, consider exploring the following courses as an excellent starting point and very helpful for your object-oriented design interviews:

Grokking the Low Level Design Interview Using OOD Principles

Cover
Grokking the Low Level Design Interview Using OOD Principles

With hundreds of potential problems to design, preparing for the object-oriented design (OOD) interview can feel like a daunting task. However, with a strategic approach, OOD interview prep doesn’t have to take more than a few weeks. In this course, you’ll learn the fundamentals of object-oriented design with an extensive set of real-world problems to help you prepare for the OOD part of a typical software engineering interview process at major tech companies like Apple, Google, Meta, Microsoft, and Amazon. By the end of this course, you will get an understanding of the essential object-oriented concepts like design principles and patterns to ace the OOD interview. You will develop the ability to efficiently breakdown an interview design problem into multiple parts using a bottom-up approach. You will be familiar with the scope of each interview problem by accurately defining the requirements and presenting its solution using class, use case, sequence, and activity diagrams.

50hrs
Intermediate
8 Playgrounds
4 Quizzes

Grokking Coding Interview Patterns in Java

Cover
Grokking the Coding Interview Patterns

With thousands of potential questions to account for, preparing for the coding interview can feel like an impossible challenge. Yet with a strategic approach, coding interview prep doesn’t have to take more than a few weeks. Stop drilling endless sets of practice problems, and prepare more efficiently by learning coding interview patterns. This course teaches you the underlying patterns behind common coding interview questions. By learning these essential patterns, you will be able to unpack and answer any problem the right way — just by assessing the problem statement. This approach was created by FAANG hiring managers to help you prepare for the typical rounds of interviews at major tech companies like Apple, Google, Meta, Microsoft, and Amazon. Before long, you will have the skills you need to unlock even the most challenging questions, grok the coding interview, and level up your career with confidence. This course is also available in JavaScript, Python, Go, and C++ — with more coming soon!

85hrs
Intermediate
234 Challenges
235 Quizzes

The Easiest Way to Learn Design Patterns in C#

Cover
The Easiest Way to Learn Design Patterns in C#

A deep understanding of design patterns and the ability to apply them to relevant design challenges is key to writing good code. They ensure the code is easy to understand and maintain, can be extended in the face of evolving requirements, and can be reused as needed. This course will help you learn design patterns by providing context for using each design pattern and mapping it to common real-world problems that developers can relate to. It also provides hands-on code examples in C# to help apply the learned concepts. The course starts by explaining the SOLID design principles for writing good code followed by discussing the classical behavioral, creational, and structural design patterns in detail. The course also focuses on their strengths and weaknesses and the recurring challenges in software design that these patterns address. After taking this course, you can use your knowledge of design patterns and best practices to create reliable, scalable, and maintainable software projects.

22hrs
Intermediate
33 Playgrounds
2 Quizzes

Master Software Design Patterns and Architecture in C++

Cover
Master Software Design Patterns and Architecture in C++

Software engineering researchers and practitioners noticed that parts of software projects could often be solved using approaches that were discovered earlier to solve similar problems. This led to the documentation of software design and architectural patterns, which are known solution approaches to a class of problems. This course starts by putting software patterns into perspective by answering the question, “What is a software pattern, and how is it different from an algorithm?” You will then learn about the two classes of patterns—software design and architecture patterns. Next, you’ll learn common C++ idioms. Finally, you will learn about software patterns for concurrency. By the end of the course, you will have useful and practical tools to improve the quality of your code and productivity. You’ll also be able to analyze a given part of your software project, pick the right pattern for the job, and apply it.

30hrs
Intermediate
96 Playgrounds
10 Quizzes


  

Free Resources