In this chapter, we explore the object-oriented design of a Parking Lot system, one of the most popular questions in technical interviews. This parking lot application aims to provide a comprehensive solution for efficiently managing a parking lot. It automates various processes, including vehicle entry, exit, and spot allocation, while also providing accurate information about parking lot occupancy and generating parking tickets.
To build this system, we first need to clarify its requirements.
New here? In plain terms, this is what we’re building and why — with an everyday analogy to anchor your intuition before the deep dive below.
Designing the parking lot is really about naming the real-world nouns and giving each one job. The cars and spots are physical things, a ticket records your visit, a manager hands out best-fit spots, and a lot out front coordinates it all. Good OOP is just modelling that real scene as cooperating objects, each with one clear responsibility.
The first step in designing the parking lot system is to clarify the requirements and define the scope. Here’s an example of a typical prompt an interviewer might present:
“Imagine you’re arriving at a busy parking lot, eager to park your car. At the entrance, you’re issued a ticket. You then drive in, find a spot suited to your vehicle’s size, and park. Later, when you prepare to leave, you present your ticket at the exit, the system calculates your fee, and the spot is freed up for the next vehicle. Behind the scenes, the parking lot is assigning spots based on vehicle size, recording entry and exit times, and updating availability for new arrivals. Now, let’s design a parking lot system that handles all this.”
In this step, we ask clarifying questions to narrow down the list of requirements, understand the constraints, and define the problem that can be solved in 30-45 minutes.
Here is an example of how a conversation between a candidate and an interviewer might unfold:
Candidate: What types of vehicles are supported by the parking lot?
Interviewer: Three types of vehicles should be supported: motorcycles, cars, and trucks.
Candidate: What parking spot types are available in the parking lot?
Interviewer: The parking lot supports three types of parking spots: compact, regular spots, and oversized.
Candidate: How does the system determine which spot a vehicle should park in?
Interviewer: The system assigns spots based on the size of the vehicle, ensuring an appropriate fit.
Candidate: Are parking tickets issued to vehicles upon entry and charged at the exit?
Interviewer: Yes, a ticket is issued with vehicle details and entry time when a vehicle enters. On exit, the system calculates the fee based on duration and vehicle size, then marks the spot as vacant.
Candidate: How are parking fees calculated?
Interviewer: Fees are based on parking duration and vehicle size, with rates varying depending on the time of day.
As we ask clarifying questions, we should note down the key requirements for this problem. Putting the key requirements in writing will help us avoid ambiguity and contradictions, as there is nothing worse than realizing you are solving the wrong problem.
Here are the key functional requirements we’ve identified:
Below are the non-functional requirements:
With these requirements set, we now identify the core objects.
Before diving into the design, it’s important to enumerate the core objects.
Design choice: We chose these five objects to separate concerns. Vehicle and ParkingSpot define the core physical entities, Ticket tracks sessions, ParkingManager handles allocation, and ParkingLot coordinates as a facade.
Note: To learn more about the Facade Pattern and its common use cases, refer to the Further Reading section at the end of this chapter.
Now that we’ve identified the core objects and their responsibilities, the next step is to design the classes and methods that bring the parking lot system to life.
We have modeled the Vehicle as an interface to set a standard for all vehicle types. It defines two key methods:
Concrete classes like Motorcycle, Car, and Truck implement the Vehicle interface, each defining its size:
Below is the representation of the Vehicle interface and its concrete classes.
Design choice: You might wonder: why use a getSize() method instead of a getType() method in the Vehicle class? Using getType() would tie us to specific vehicle names like "Motorcycle" or "Car", forcing updates to the system’s logic every time a new type (say, "Scooter") comes along. For example, fee calculations or spot assignments would need new cases for each type. With getSize(), we abstract that away. The parking lot cares more about the size of a vehicle, such as small, medium, or large, than its exact type. A truck and a van might both be large, so they’re treated the same for parking purposes. Adding an electric scooter? Just mark its size as small, and it fits in like a motorcycle. This keeps the system lean and adaptable, focusing on space over semantics.
The ParkingSpot interface represents a parking spot in the parking lot system. It captures spot-specific details, such as whether it’s occupied and its size. Concrete parking spot types (CompactSpot, RegularSpot, and OversizedSpot) are implemented as classes that adhere to the ParkingSpot interface. These classes bring the interface to life, defining spots for small, medium, and large vehicles, respectively.
The UML diagram below illustrates this structure.
Design choice: The ParkingSpot class is intentionally designed to be simple, only encompassing its state (e.g., availability and size). The ParkingManager class is responsible for more complex operations, such as locating available parking spots and monitoring parked vehicles. This design choice promotes adding new spot types without introducing unnecessary complexity.
The ParkingManager is responsible for managing the allocation and tracking of parking spots within the parking lot system. Its primary functions include identifying available parking spaces, assigning the most suitable spot for each vehicle, and maintaining a record of parked vehicles and their locations. These tasks are accomplished through two key methods.
Here is the representation of the ParkingManager class.
Design choice: The ParkingManager class is designed to encapsulate the logic for parking spot allocation, deallocation, and tracking within the parking lot system. This centralization ensures that the ParkingLot class operates as a lightweight facade, focusing solely on orchestrating high-level operations such as vehicle entry, ticketing, and exit processing. By delegating spot management to ParkingManager, the system maintains a clear separation of concerns, enhancing modularity and scalability.
The Ticket class represents a parking ticket generated when a vehicle enters the parking lot. It keeps track of when a vehicle arrives and leaves, using these times to calculate duration, and links the vehicle to its assigned spot.
Below is the representation of the Ticket class.
Design choice: The Ticket class is designed as a concise, immutable record of a parking event, capturing essential details such as the ticket ID, associated Vehicle, assigned ParkingSpot, entry time, and exit time. Its primary role is to serve as a data container, ensuring simplicity and focus by delegating complex logic, such as parking fee calculation, to the FareCalculator class.
We design the FareStrategy interface to establish a standard method for modifying the parking fee, allowing various pricing rules to fit into the system. Its concrete classes handle specific pricing rules:
Since a parking session often involves multiple pricing rules, like duration, size, and time, we design a FareCalculator class to coordinate these changes and calculate the final fee. It is designed to determine the cost for each ticket by combining the effects of all applicable strategies (BaseFareStrategy, PeakHoursFareStrategy), ensuring the system applies the right fee based on how long the vehicle stays, its size, and when it is parked.
This association between FareStrategy and FareCalculator maintains a structured pricing process, with FareStrategy defining the rules and FareCalculator pulling them together.
The pricing logic relies on the Strategy Pattern, which enables the system to dynamically select and swap between different rules for calculating parking fees.
Note: To learn more about the Strategy Pattern and its common use cases, refer to the Further Reading section at the end of this chapter.
The UML diagram below illustrates this structure.
Design choice: The FareStrategy interface encapsulates pricing logic for the parking lot system, enabling modular and interchangeable rules for calculating parking fees. By defining a standard contract for pricing strategies (e.g., BaseFareStrategy, PeakHoursFareStrategy), it ensures that the ParkingLot facade remains lightweight, delegating fee calculations to the FareCalculator class, which orchestrates these strategies. This design, rooted in the Strategy Pattern, promotes flexibility, maintainability, and extensibility while keeping the system’s core logic clean and focused.
We design the ParkingLot class as the core component of the system to act as a facade, providing a simple interface for managing the parking lot’s key operations. It manages vehicle entry and exit by generating tickets for arrivals, assigning spots through the ParkingManager, and calculating fares with the FareCalculator when vehicles leave, tying the system’s main functions together.
Below is the representation of this class.
Next, we’ll connect these objects in a class diagram to visualize their relationships.
Take a moment to review the complete class structure and the relationships between them. This diagram demonstrates how a seemingly complex system can be constructed using simple, well-designed components working together cohesively.
With this design in place, we move to implementation.
In this section, we’ll implement the core functionalities of the parking lot system, focusing on key areas such as managing vehicle entry and exit, assigning parking spots efficiently, and calculating parking fees accurately.
We define the Vehicle interface, along with its supporting VehicleSize enum and concrete classes Motorcycle, Car, and Truck, to set up how vehicles are identified and sized in the parking lot system.
Here is the implementation of this interface and its concrete classes.
public interface Vehicle {
String getLicensePlate();
VehicleSize getSize();
}
public class Car implements Vehicle {
private String licensePlate;
public Car(String licensePlate) {
this.licensePlate = licensePlate;
}
@Override
public String getLicensePlate() {
return this.licensePlate;
}
@Override
public VehicleSize getSize() {
return VehicleSize.MEDIUM;
}
}
public enum VehicleSize {
SMALL,
MEDIUM,
LARGE
}
This interface ensures every vehicle provides two key attributes: a license plate for tracking and a size for managing parking spaces. This design ensures that every vehicle provides consistent, type-safe attributes critical for tracking, parking spot allocation, and fee calculation
For the sake of brevity, we have not shown the code for the Motorcycle and Truck classes.
Implementation choice: The VehicleSize enum (SMALL, MEDIUM, LARGE) standardizes vehicle and parking spot sizes, ensuring type-safe, error-free size comparisons for efficient spot allocation and fee calculation.
Alternatives and trade-offs:
We define the ParkingSpot interface to represent individual parking spots in the parking lot system, along with its concrete classes CompactSpot, RegularSpot, and OversizedSpot.
Here’s the code for the ParkingSpot interface:
public interface ParkingSpot {
boolean isAvailable();
void occupy(Vehicle vehicle);
void vacate();
int getSpotNumber();
VehicleSize getSize();
}
isAvailable(): Checks if the spot is free. Helps ParkingManager decide if the spot can be assigned.
occupy(Vehicle vehicle): Assigns a vehicle to the spot if it’s available, setting vehicle to the provided instance.
vacate(): Clears the spot by setting the vehicle to null, making the spot free for reuse. Allows ParkingManager to reassign it to another vehicle.
getSize(): Returns the spot’s fixed VehicleSize (e.g., SMALL for CompactSpot). Guides ParkingManager in matching vehicle sizes to parking spot capacities.
The concrete class CompactSpot implements this interface:
public class CompactSpot implements ParkingSpot {
private int spotNumber;
private Vehicle vehicle; // The vehicle currently occupying this spot
public CompactSpot(int spotNumber) {
this.spotNumber = spotNumber;
this.vehicle = null; // No vehicle occupying initially
}
@Override
public int getSpotNumber() {
return spotNumber;
}
@Override
public boolean isAvailable() {
return vehicle == null;
}
@Override
public void occupy(Vehicle vehicle) {
if (isAvailable()) {
this.vehicle = vehicle;
} else {
// Spot is already occupied.
}
}
@Override
public void vacate() {
this.vehicle = null; // Make the spot available
}
@Override
public VehicleSize getSize() {
return VehicleSize.SMALL; // Compact spots fit small vehicles
}
}
For brevity, we omit the full code of RegularSpot and OversizedSpot, but they follow a similar structure:
This implementation keeps ParkingSpot lean and focused, managing its state while delegating allocation logic to ParkingManager.
The ParkingManager class manages the allocation and tracking of parking spots in the parking lot system. It searches and assigns spots to vehicles, freeing them when vehicles leave and keeping an accurate record of which vehicles occupy which parking spots.
Here’s the implementation of this class:
public class ParkingManager {
private final Map<VehicleSize, List<ParkingSpot>> availableSpots;
private final Map<Vehicle, ParkingSpot> vehicleToSpotMap;
// Create Parking Manager based on a given map of available spots
public ParkingManager(Map<VehicleSize, List<ParkingSpot>> availableSpots) {
this.availableSpots = availableSpots;
this.vehicleToSpotMap = new HashMap<>();
}
public ParkingSpot findSpotForVehicle(Vehicle vehicle) {
VehicleSize vehicleSize = vehicle.getSize();
// Start looking for the smallest spot that can fit the vehicle
for (VehicleSize size : VehicleSize.values()) {
if (size.ordinal() >= vehicleSize.ordinal()) {
List<ParkingSpot> spots = availableSpots.get(size);
for (ParkingSpot spot : spots) {
if (spot.isAvailable()) {
return spot; // Return the first available spot
}
}
}
}
return null; // No suitable spot found
}
public ParkingSpot parkVehicle(Vehicle vehicle) {
ParkingSpot spot = findSpotForVehicle(vehicle);
if (spot != null) {
spot.occupy(vehicle); // Record the parking spot for the vehicle
vehicleToSpotMap.put(vehicle, spot); // Remove the spot from the available list
availableSpots.get(spot.getSize()).remove(spot);
return spot; // Parking successful
}
return null; // No spot found for this vehicle
}
public void unparkVehicle(Vehicle vehicle) {
ParkingSpot spot = vehicleToSpotMap.remove(vehicle);
if (spot != null) {
spot.vacate();
availableSpots.get(spot.getSize()).add(spot);
}
}
}
findSpotForVehicle(Vehicle vehicle):
parkVehicle(Vehicle vehicle):
unparkVehicle(Vehicle vehicle):
Implementation choice:
As shown in the code above, we used two HashMaps. Let’s understand their purpose.
Here’s why these choices matter:
The Ticket class acts as a record of a parking event, linking a vehicle to its parking spot and tracking the time spent in the parking lot.
Below is the implementation of this class.
public class Ticket {
private final String ticketId; // Unique ticket identifier
private final Vehicle vehicle; // The vehicle associated with the ticket
// The parking spot where the vehicle is parked private final ParkingSpot parkingSpot;
// // The time the vehicle entered the parking lot
private final LocalDateTime entryTime; // The time the vehicle exited the parking lot
private LocalDateTime exitTime;
public Ticket(
String ticketId, Vehicle vehicle, ParkingSpot parkingSpot, LocalDateTime entryTime) {
this.ticketId = ticketId;
this.vehicle = vehicle;
this.parkingSpot = parkingSpot;
this.entryTime = entryTime;
// Initially, exitTime is null because the vehicle is still parked this.exitTime =
// null;
}
public BigDecimal calculateParkingDuration() {
return new BigDecimal(
Duration.between(
entryTime,
Objects.requireNonNullElseGet(exitTime, LocalDateTime::now))
.toMinutes());
} // getter and setter methods are omitted for brevity
}
We implement the FareStrategy interface and its concrete classes, BaseFareStrategy and PeakHoursFareStrategy, along with the FareCalculator class. These components manage the parking fee calculation process in the parking lot system. Together, they determine the cost of each parking session.
Here’s the code for the FareStrategy interface:
public interface FareStrategy {
BigDecimal calculateFare(Ticket ticket, BigDecimal inputFare);
}
Implementation choice: We define FareStrategy as an interface to support a flexible and extensible approach to pricing rules, allowing new strategies (e.g., a WeekendDiscountStrategy) to integrate without altering existing code.
The concrete class BaseFareStrategy implements this interface:
public class BaseFareStrategy implements FareStrategy {
private static final BigDecimal SMALL_VEHICLE_RATE = new BigDecimal("1.0");
private static final BigDecimal MEDIUM_VEHICLE_RATE = new BigDecimal("2.0");
private static final BigDecimal LARGE_VEHICLE_RATE = new BigDecimal("3.0");
// Calculate fare based on the duration and add it to the input fare to return a new total
@Override
public BigDecimal calculateFare(Ticket ticket, BigDecimal inputFare) {
BigDecimal fare = inputFare;
BigDecimal rate;
switch (ticket.getVehicle().getSize()) {
case MEDIUM:
rate = MEDIUM_VEHICLE_RATE;
break;
case LARGE:
rate = LARGE_VEHICLE_RATE;
break;
default:
rate = SMALL_VEHICLE_RATE;
}
fare = fare.add(rate.multiply(ticket.calculateParkingDuration()));
return fare;
}
}
calculateFare(Ticket ticket, BigDecimal inputFare): Provides the foundational cost for the parking session, reflecting size-based pricing.
The concrete class PeakHoursFareStrategy implements this interface:
public class PeakHoursFareStrategy implements FareStrategy {
// 50% higher during peak hours private static final BigDecimal PEAK_HOURS_MULTIPLIER = new
// BigDecimal("1.5");
public PeakHoursFareStrategy() {}
@Override
public BigDecimal calculateFare(Ticket ticket, BigDecimal inputFare) {
BigDecimal fare = inputFare;
if (isPeakHours(ticket.getEntryTime())) {
fare = fare.multiply(PEAK_HOURS_MULTIPLIER);
}
return fare;
}
private boolean isPeakHours(LocalDateTime time) {
int hour = time.getHour();
return (hour >= 7 && hour <= 10) || (hour >= 16 && hour <= 19);
}
}
calculateFare(Ticket ticket, BigDecimal inputFare):
isPeakHours(LocalDateTime time): Checks if the given time’s hour is within peak ranges.
The FareCalculator class uses these strategies:
public class FareCalculator {
private final List<FareStrategy> fareStrategies;
public FareCalculator(List<FareStrategy> fareStrategies) {
this.fareStrategies = fareStrategies;
}
public BigDecimal calculateFare(Ticket ticket) {
BigDecimal fare = BigDecimal.ZERO;
for (FareStrategy strategy : fareStrategies) {
fare = strategy.calculateFare(ticket, fare);
}
return fare;
}
}
FareCalculator(List<FareStrategy> fareStrategies): Initializes with a list of strategies, setting up the rules to apply during fare calculation.
calculateFare(Ticket ticket): Starts with a zero fare, iterates through each strategy in the list, and applies their rules in sequence to build the final fare.
Implementation choice: We implement FareCalculator using a List<FareStrategy> to hold strategies, enabling the sequential application of multiple rules (e.g., base fare followed by peak adjustment). We choose List over an array or Set because it preserves order. Strategies like BaseFareStrategy must be applied before PeakHoursFareStrategy for correct fare calculation. A Set can prevent duplicates but loses order, while an array maintains a fixed size, limiting flexibility.
The ParkingLot class acts as a facade, providing a simple interface for clients to interact with the parking lot system while delegating complex tasks to ParkingManager and FareCalculator. It relies on ParkingManager for spot allocation and FareCalculator for pricing, managing the flow of vehicles through entry and exit operations.
Here’s the implementation of the ParkingLot class:
public class ParkingLot {
// Manages parking spots and vehicle assignments private final ParkingManager
// parkingManager;
// Calculates fare for parking sessions private final FareCalculator fareCalculator;
public ParkingLot(ParkingManager parkingManager, FareCalculator fareCalculator) {
this.parkingManager = parkingManager;
this.fareCalculator = fareCalculator;
}
// Method to handle vehicle entry into the parking lot
public Ticket enterVehicle(Vehicle vehicle) {
// Delegate parking logic to ParkingManager
ParkingSpot spot = parkingManager.parkVehicle(vehicle);
if (spot != null) {
// Create ticket with entry time
Ticket ticket = new Ticket(generateTicketId(), vehicle, spot, LocalDateTime.now());
return ticket;
} else {
return null; // No spot available
}
}
// Method to handle vehicle exit from the parking lot
public void leaveVehicle(Ticket ticket) {
// Ensure the ticket is valid and the vehicle hasn't already left
if (ticket != null && ticket.getExitTime() == null) {
// Set exit time
ticket.setExitTime(LocalDateTime.now());
// Delegate unparking logic to ParkingManager
parkingManager.unparkVehicle(ticket.getVehicle());
// Calculate the fare
BigDecimal fare = fareCalculator.calculateFare(ticket);
} else {
// Invalid ticket or vehicle already exited.
}
}
}
enterVehicle(Vehicle vehicle): Coordinates vehicle entry by requesting a parking spot from ParkingManager. It then generates a Ticket with a unique ID, vehicle, parking spot, and current entry time.
leaveVehicle(Ticket ticket): Manages vehicle exit by setting the exit time, frees the parking spot via ParkingManager, and calculates the fare with FareCalculator.
Below is a complete, runnable version of the design above — vehicles, spots, a ParkingManager, tickets, the two fare strategies, and the ParkingLot facade — wired together with a small demo that parks a car and a truck and computes their fees. All three languages produce the same output.
from abc import ABC, abstractmethod
from enum import IntEnum
class VehicleSize(IntEnum):
SMALL = 0
MEDIUM = 1
LARGE = 2
# ---------- Vehicle: an interface with three concrete types ----------
class Vehicle(ABC):
@abstractmethod
def get_license_plate(self) -> str: ...
@abstractmethod
def get_size(self) -> VehicleSize: ...
class Motorcycle(Vehicle):
def __init__(self, plate): self._plate = plate
def get_license_plate(self): return self._plate
def get_size(self): return VehicleSize.SMALL
class Car(Vehicle):
def __init__(self, plate): self._plate = plate
def get_license_plate(self): return self._plate
def get_size(self): return VehicleSize.MEDIUM
class Truck(Vehicle):
def __init__(self, plate): self._plate = plate
def get_license_plate(self): return self._plate
def get_size(self): return VehicleSize.LARGE
# ---------- ParkingSpot: a base with size-specific subclasses ----------
class ParkingSpot(ABC):
def __init__(self, spot_number):
self._spot_number = spot_number
self._vehicle = None # None means the spot is free
def is_available(self): return self._vehicle is None
def occupy(self, vehicle):
if self.is_available():
self._vehicle = vehicle
def vacate(self): self._vehicle = None
def get_spot_number(self): return self._spot_number
@abstractmethod
def get_size(self) -> VehicleSize: ...
class CompactSpot(ParkingSpot):
def get_size(self): return VehicleSize.SMALL
class RegularSpot(ParkingSpot):
def get_size(self): return VehicleSize.MEDIUM
class OversizedSpot(ParkingSpot):
def get_size(self): return VehicleSize.LARGE
# ---------- ParkingManager: allocates and frees spots ----------
class ParkingManager:
def __init__(self, available_spots):
# available_spots: dict[VehicleSize, list[ParkingSpot]]
self._available = available_spots
self._vehicle_to_spot = {}
def find_spot_for_vehicle(self, vehicle):
# Smallest spot that still fits the vehicle.
for size in VehicleSize: # SMALL, MEDIUM, LARGE in order
if size >= vehicle.get_size():
for spot in self._available.get(size, []):
if spot.is_available():
return spot
return None
def park_vehicle(self, vehicle):
spot = self.find_spot_for_vehicle(vehicle)
if spot is not None:
spot.occupy(vehicle)
self._vehicle_to_spot[vehicle] = spot
self._available[spot.get_size()].remove(spot)
return spot
return None
def unpark_vehicle(self, vehicle):
spot = self._vehicle_to_spot.pop(vehicle, None)
if spot is not None:
spot.vacate()
self._available[spot.get_size()].append(spot)
def available_count(self):
return sum(len(spots) for spots in self._available.values())
# ---------- Ticket: a record of one parking session ----------
class Ticket:
def __init__(self, ticket_id, vehicle, spot, entry_time):
self.ticket_id = ticket_id
self.vehicle = vehicle
self.spot = spot
self.entry_time = entry_time # minutes since midnight
self.exit_time = -1 # -1 while still parked
def calculate_parking_duration(self):
end = self.exit_time if self.exit_time >= 0 else self.entry_time
return end - self.entry_time
# ---------- Fare strategies (Strategy pattern) ----------
class FareStrategy(ABC):
@abstractmethod
def calculate_fare(self, ticket, input_fare): ...
class BaseFareStrategy(FareStrategy):
RATES = {VehicleSize.SMALL: 1.0, VehicleSize.MEDIUM: 2.0, VehicleSize.LARGE: 3.0}
def calculate_fare(self, ticket, input_fare):
rate = self.RATES[ticket.vehicle.get_size()]
return input_fare + rate * ticket.calculate_parking_duration()
class PeakHoursFareStrategy(FareStrategy):
MULTIPLIER = 1.5
def calculate_fare(self, ticket, input_fare):
if self._is_peak(ticket.entry_time):
return input_fare * self.MULTIPLIER
return input_fare
def _is_peak(self, minutes):
hour = (minutes // 60) % 24
return (7 <= hour <= 10) or (16 <= hour <= 19)
class FareCalculator:
def __init__(self, strategies):
self._strategies = strategies
def calculate_fare(self, ticket):
fare = 0.0
for strategy in self._strategies:
fare = strategy.calculate_fare(ticket, fare)
return fare
# ---------- ParkingLot: the facade clients talk to ----------
class ParkingLot:
def __init__(self, manager, calculator):
self._manager = manager
self._calculator = calculator
self._counter = 0
def _generate_ticket_id(self):
self._counter += 1
return "T%03d" % self._counter
def enter_vehicle(self, vehicle, entry_time):
spot = self._manager.park_vehicle(vehicle)
if spot is None:
return None
return Ticket(self._generate_ticket_id(), vehicle, spot, entry_time)
def leave_vehicle(self, ticket, exit_time):
if ticket is None or ticket.exit_time >= 0:
return 0.0
ticket.exit_time = exit_time
self._manager.unpark_vehicle(ticket.vehicle)
return self._calculator.calculate_fare(ticket)
def make_time(hour, minute):
return hour * 60 + minute
def main():
available = {
VehicleSize.SMALL: [CompactSpot(1), CompactSpot(2)],
VehicleSize.MEDIUM: [RegularSpot(3), RegularSpot(4)],
VehicleSize.LARGE: [OversizedSpot(5)],
}
manager = ParkingManager(available)
calculator = FareCalculator([BaseFareStrategy(), PeakHoursFareStrategy()])
lot = ParkingLot(manager, calculator)
print("Spots available at open: %d" % manager.available_count())
car = Car("CAR-1")
t1 = lot.enter_vehicle(car, make_time(9, 0)) # 09:00, peak
print("%s -> ticket %s, spot #%d" % (car.get_license_plate(), t1.ticket_id, t1.spot.get_spot_number()))
truck = Truck("TRK-9")
t2 = lot.enter_vehicle(truck, make_time(12, 0)) # noon, off-peak
print("%s -> ticket %s, spot #%d" % (truck.get_license_plate(), t2.ticket_id, t2.spot.get_spot_number()))
print("Spots available now: %d" % manager.available_count())
fare1 = lot.leave_vehicle(t1, make_time(10, 30)) # 90 min, peak
print("%s parked 90 min in peak -> $%.2f" % (car.get_license_plate(), fare1))
fare2 = lot.leave_vehicle(t2, make_time(12, 30)) # 30 min, off-peak
print("%s parked 30 min off-peak -> $%.2f" % (truck.get_license_plate(), fare2))
print("Spots available at close: %d" % manager.available_count())
if __name__ == "__main__":
main()#include <iostream>
#include <string>
#include <vector>
#include <map>
#include <cstdio>
enum class VehicleSize { SMALL = 0, MEDIUM = 1, LARGE = 2 };
// ---------- Vehicle: a base class with three concrete types ----------
class Vehicle {
public:
virtual std::string getLicensePlate() const = 0;
virtual VehicleSize getSize() const = 0;
virtual ~Vehicle() {}
};
class Motorcycle : public Vehicle {
std::string plate;
public:
Motorcycle(std::string plate) : plate(plate) {}
std::string getLicensePlate() const override { return plate; }
VehicleSize getSize() const override { return VehicleSize::SMALL; }
};
class Car : public Vehicle {
std::string plate;
public:
Car(std::string plate) : plate(plate) {}
std::string getLicensePlate() const override { return plate; }
VehicleSize getSize() const override { return VehicleSize::MEDIUM; }
};
class Truck : public Vehicle {
std::string plate;
public:
Truck(std::string plate) : plate(plate) {}
std::string getLicensePlate() const override { return plate; }
VehicleSize getSize() const override { return VehicleSize::LARGE; }
};
// ---------- ParkingSpot: a base with size-specific subclasses ----------
class ParkingSpot {
public:
virtual bool isAvailable() const = 0;
virtual void occupy(Vehicle* vehicle) = 0;
virtual void vacate() = 0;
virtual int getSpotNumber() const = 0;
virtual VehicleSize getSize() const = 0;
virtual ~ParkingSpot() {}
};
class AbstractSpot : public ParkingSpot {
protected:
int spotNumber;
Vehicle* vehicle; // nullptr means the spot is free
public:
AbstractSpot(int spotNumber) : spotNumber(spotNumber), vehicle(nullptr) {}
bool isAvailable() const override { return vehicle == nullptr; }
void occupy(Vehicle* v) override { if (isAvailable()) vehicle = v; }
void vacate() override { vehicle = nullptr; }
int getSpotNumber() const override { return spotNumber; }
};
class CompactSpot : public AbstractSpot {
public:
CompactSpot(int n) : AbstractSpot(n) {}
VehicleSize getSize() const override { return VehicleSize::SMALL; }
};
class RegularSpot : public AbstractSpot {
public:
RegularSpot(int n) : AbstractSpot(n) {}
VehicleSize getSize() const override { return VehicleSize::MEDIUM; }
};
class OversizedSpot : public AbstractSpot {
public:
OversizedSpot(int n) : AbstractSpot(n) {}
VehicleSize getSize() const override { return VehicleSize::LARGE; }
};
// ---------- ParkingManager: allocates and frees spots ----------
class ParkingManager {
std::map<VehicleSize, std::vector<ParkingSpot*>> available;
std::map<Vehicle*, ParkingSpot*> vehicleToSpot;
public:
ParkingManager(std::map<VehicleSize, std::vector<ParkingSpot*>> available)
: available(available) {}
ParkingSpot* findSpotForVehicle(Vehicle* vehicle) {
VehicleSize needed = vehicle->getSize();
VehicleSize order[] = { VehicleSize::SMALL, VehicleSize::MEDIUM, VehicleSize::LARGE };
for (VehicleSize size : order) {
if (static_cast<int>(size) >= static_cast<int>(needed)) {
for (ParkingSpot* spot : available[size]) {
if (spot->isAvailable()) return spot;
}
}
}
return nullptr;
}
ParkingSpot* parkVehicle(Vehicle* vehicle) {
ParkingSpot* spot = findSpotForVehicle(vehicle);
if (spot != nullptr) {
spot->occupy(vehicle);
vehicleToSpot[vehicle] = spot;
std::vector<ParkingSpot*>& list = available[spot->getSize()];
for (std::size_t i = 0; i < list.size(); ++i) {
if (list[i] == spot) { list.erase(list.begin() + i); break; }
}
return spot;
}
return nullptr;
}
void unparkVehicle(Vehicle* vehicle) {
std::map<Vehicle*, ParkingSpot*>::iterator it = vehicleToSpot.find(vehicle);
if (it != vehicleToSpot.end()) {
ParkingSpot* spot = it->second;
vehicleToSpot.erase(it);
spot->vacate();
available[spot->getSize()].push_back(spot);
}
}
int availableCount() {
int count = 0;
for (std::map<VehicleSize, std::vector<ParkingSpot*>>::iterator it = available.begin();
it != available.end(); ++it) {
count += static_cast<int>(it->second.size());
}
return count;
}
};
// ---------- Ticket: a record of one parking session ----------
class Ticket {
public:
std::string ticketId;
Vehicle* vehicle;
ParkingSpot* spot;
int entryTime; // minutes since midnight
int exitTime; // -1 while still parked
Ticket(std::string ticketId, Vehicle* vehicle, ParkingSpot* spot, int entryTime)
: ticketId(ticketId), vehicle(vehicle), spot(spot), entryTime(entryTime), exitTime(-1) {}
int calculateParkingDuration() {
int end = (exitTime >= 0) ? exitTime : entryTime;
return end - entryTime;
}
};
// ---------- Fare strategies (Strategy pattern) ----------
class FareStrategy {
public:
virtual double calculateFare(Ticket* ticket, double inputFare) = 0;
virtual ~FareStrategy() {}
};
class BaseFareStrategy : public FareStrategy {
public:
double calculateFare(Ticket* ticket, double inputFare) override {
double rate;
switch (ticket->vehicle->getSize()) {
case VehicleSize::MEDIUM: rate = 2.0; break;
case VehicleSize::LARGE: rate = 3.0; break;
default: rate = 1.0; break;
}
return inputFare + rate * ticket->calculateParkingDuration();
}
};
class PeakHoursFareStrategy : public FareStrategy {
bool isPeak(int minutes) {
int hour = (minutes / 60) % 24;
return (hour >= 7 && hour <= 10) || (hour >= 16 && hour <= 19);
}
public:
double calculateFare(Ticket* ticket, double inputFare) override {
if (isPeak(ticket->entryTime)) return inputFare * 1.5;
return inputFare;
}
};
class FareCalculator {
std::vector<FareStrategy*> strategies;
public:
FareCalculator(std::vector<FareStrategy*> strategies) : strategies(strategies) {}
double calculateFare(Ticket* ticket) {
double fare = 0.0;
for (FareStrategy* strategy : strategies) fare = strategy->calculateFare(ticket, fare);
return fare;
}
};
// ---------- ParkingLot: the facade clients talk to ----------
class ParkingLot {
ParkingManager* manager;
FareCalculator* calculator;
int counter;
public:
ParkingLot(ParkingManager* manager, FareCalculator* calculator)
: manager(manager), calculator(calculator), counter(0) {}
std::string generateTicketId() {
char buffer[8];
std::snprintf(buffer, sizeof(buffer), "T%03d", ++counter);
return std::string(buffer);
}
Ticket* enterVehicle(Vehicle* vehicle, int entryTime) {
ParkingSpot* spot = manager->parkVehicle(vehicle);
if (spot == nullptr) return nullptr;
return new Ticket(generateTicketId(), vehicle, spot, entryTime);
}
double leaveVehicle(Ticket* ticket, int exitTime) {
if (ticket == nullptr || ticket->exitTime >= 0) return 0.0;
ticket->exitTime = exitTime;
manager->unparkVehicle(ticket->vehicle);
return calculator->calculateFare(ticket);
}
};
static int makeTime(int hour, int minute) { return hour * 60 + minute; }
int main() {
std::map<VehicleSize, std::vector<ParkingSpot*>> available;
available[VehicleSize::SMALL] = { new CompactSpot(1), new CompactSpot(2) };
available[VehicleSize::MEDIUM] = { new RegularSpot(3), new RegularSpot(4) };
available[VehicleSize::LARGE] = { new OversizedSpot(5) };
ParkingManager manager(available);
std::vector<FareStrategy*> strategies = { new BaseFareStrategy(), new PeakHoursFareStrategy() };
FareCalculator calculator(strategies);
ParkingLot lot(&manager, &calculator);
std::cout << "Spots available at open: " << manager.availableCount() << "\n";
Vehicle* car = new Car("CAR-1");
Ticket* t1 = lot.enterVehicle(car, makeTime(9, 0)); // 09:00, peak
std::cout << car->getLicensePlate() << " -> ticket " << t1->ticketId
<< ", spot #" << t1->spot->getSpotNumber() << "\n";
Vehicle* truck = new Truck("TRK-9");
Ticket* t2 = lot.enterVehicle(truck, makeTime(12, 0)); // noon, off-peak
std::cout << truck->getLicensePlate() << " -> ticket " << t2->ticketId
<< ", spot #" << t2->spot->getSpotNumber() << "\n";
std::cout << "Spots available now: " << manager.availableCount() << "\n";
double fare1 = lot.leaveVehicle(t1, makeTime(10, 30)); // 90 min, peak
std::printf("%s parked 90 min in peak -> $%.2f\n", car->getLicensePlate().c_str(), fare1);
double fare2 = lot.leaveVehicle(t2, makeTime(12, 30)); // 30 min, off-peak
std::printf("%s parked 30 min off-peak -> $%.2f\n", truck->getLicensePlate().c_str(), fare2);
std::cout << "Spots available at close: " << manager.availableCount() << "\n";
return 0;
}import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
enum VehicleSize { SMALL, MEDIUM, LARGE }
// ---------- Vehicle: an interface with three concrete types ----------
interface Vehicle {
String getLicensePlate();
VehicleSize getSize();
}
class Motorcycle implements Vehicle {
private final String plate;
Motorcycle(String plate) { this.plate = plate; }
public String getLicensePlate() { return plate; }
public VehicleSize getSize() { return VehicleSize.SMALL; }
}
class Car implements Vehicle {
private final String plate;
Car(String plate) { this.plate = plate; }
public String getLicensePlate() { return plate; }
public VehicleSize getSize() { return VehicleSize.MEDIUM; }
}
class Truck implements Vehicle {
private final String plate;
Truck(String plate) { this.plate = plate; }
public String getLicensePlate() { return plate; }
public VehicleSize getSize() { return VehicleSize.LARGE; }
}
// ---------- ParkingSpot: a base with size-specific subclasses ----------
interface ParkingSpot {
boolean isAvailable();
void occupy(Vehicle vehicle);
void vacate();
int getSpotNumber();
VehicleSize getSize();
}
abstract class AbstractSpot implements ParkingSpot {
private final int spotNumber;
private Vehicle vehicle; // null means the spot is free
AbstractSpot(int spotNumber) { this.spotNumber = spotNumber; }
public boolean isAvailable() { return vehicle == null; }
public void occupy(Vehicle vehicle) { if (isAvailable()) this.vehicle = vehicle; }
public void vacate() { this.vehicle = null; }
public int getSpotNumber() { return spotNumber; }
}
class CompactSpot extends AbstractSpot {
CompactSpot(int n) { super(n); }
public VehicleSize getSize() { return VehicleSize.SMALL; }
}
class RegularSpot extends AbstractSpot {
RegularSpot(int n) { super(n); }
public VehicleSize getSize() { return VehicleSize.MEDIUM; }
}
class OversizedSpot extends AbstractSpot {
OversizedSpot(int n) { super(n); }
public VehicleSize getSize() { return VehicleSize.LARGE; }
}
// ---------- ParkingManager: allocates and frees spots ----------
class ParkingManager {
private final Map<VehicleSize, List<ParkingSpot>> available;
private final Map<Vehicle, ParkingSpot> vehicleToSpot = new HashMap<>();
ParkingManager(Map<VehicleSize, List<ParkingSpot>> available) {
this.available = available;
}
ParkingSpot findSpotForVehicle(Vehicle vehicle) {
// Smallest spot that still fits the vehicle.
for (VehicleSize size : VehicleSize.values()) {
if (size.ordinal() >= vehicle.getSize().ordinal()) {
for (ParkingSpot spot : available.get(size)) {
if (spot.isAvailable()) return spot;
}
}
}
return null;
}
ParkingSpot parkVehicle(Vehicle vehicle) {
ParkingSpot spot = findSpotForVehicle(vehicle);
if (spot != null) {
spot.occupy(vehicle);
vehicleToSpot.put(vehicle, spot);
available.get(spot.getSize()).remove(spot);
return spot;
}
return null;
}
void unparkVehicle(Vehicle vehicle) {
ParkingSpot spot = vehicleToSpot.remove(vehicle);
if (spot != null) {
spot.vacate();
available.get(spot.getSize()).add(spot);
}
}
int availableCount() {
int count = 0;
for (List<ParkingSpot> spots : available.values()) count += spots.size();
return count;
}
}
// ---------- Ticket: a record of one parking session ----------
class Ticket {
final String ticketId;
final Vehicle vehicle;
final ParkingSpot spot;
final int entryTime; // minutes since midnight
int exitTime = -1; // -1 while still parked
Ticket(String ticketId, Vehicle vehicle, ParkingSpot spot, int entryTime) {
this.ticketId = ticketId;
this.vehicle = vehicle;
this.spot = spot;
this.entryTime = entryTime;
}
int calculateParkingDuration() {
int end = (exitTime >= 0) ? exitTime : entryTime;
return end - entryTime;
}
}
// ---------- Fare strategies (Strategy pattern) ----------
interface FareStrategy {
double calculateFare(Ticket ticket, double inputFare);
}
class BaseFareStrategy implements FareStrategy {
public double calculateFare(Ticket ticket, double inputFare) {
double rate;
switch (ticket.vehicle.getSize()) {
case MEDIUM: rate = 2.0; break;
case LARGE: rate = 3.0; break;
default: rate = 1.0;
}
return inputFare + rate * ticket.calculateParkingDuration();
}
}
class PeakHoursFareStrategy implements FareStrategy {
public double calculateFare(Ticket ticket, double inputFare) {
return isPeak(ticket.entryTime) ? inputFare * 1.5 : inputFare;
}
private boolean isPeak(int minutes) {
int hour = (minutes / 60) % 24;
return (hour >= 7 && hour <= 10) || (hour >= 16 && hour <= 19);
}
}
class FareCalculator {
private final List<FareStrategy> strategies;
FareCalculator(List<FareStrategy> strategies) { this.strategies = strategies; }
double calculateFare(Ticket ticket) {
double fare = 0.0;
for (FareStrategy strategy : strategies) fare = strategy.calculateFare(ticket, fare);
return fare;
}
}
// ---------- ParkingLot: the facade clients talk to ----------
class ParkingLot {
private final ParkingManager manager;
private final FareCalculator calculator;
private int counter = 0;
ParkingLot(ParkingManager manager, FareCalculator calculator) {
this.manager = manager;
this.calculator = calculator;
}
private String generateTicketId() { return String.format("T%03d", ++counter); }
Ticket enterVehicle(Vehicle vehicle, int entryTime) {
ParkingSpot spot = manager.parkVehicle(vehicle);
if (spot == null) return null;
return new Ticket(generateTicketId(), vehicle, spot, entryTime);
}
double leaveVehicle(Ticket ticket, int exitTime) {
if (ticket == null || ticket.exitTime >= 0) return 0.0;
ticket.exitTime = exitTime;
manager.unparkVehicle(ticket.vehicle);
return calculator.calculateFare(ticket);
}
}
public class Main {
static int makeTime(int hour, int minute) { return hour * 60 + minute; }
public static void main(String[] args) {
Map<VehicleSize, List<ParkingSpot>> available = new HashMap<>();
available.put(VehicleSize.SMALL, new ArrayList<>(List.of(new CompactSpot(1), new CompactSpot(2))));
available.put(VehicleSize.MEDIUM, new ArrayList<>(List.of(new RegularSpot(3), new RegularSpot(4))));
available.put(VehicleSize.LARGE, new ArrayList<>(List.of(new OversizedSpot(5))));
ParkingManager manager = new ParkingManager(available);
FareCalculator calculator = new FareCalculator(List.of(new BaseFareStrategy(), new PeakHoursFareStrategy()));
ParkingLot lot = new ParkingLot(manager, calculator);
System.out.println("Spots available at open: " + manager.availableCount());
Vehicle car = new Car("CAR-1");
Ticket t1 = lot.enterVehicle(car, makeTime(9, 0)); // 09:00, peak
System.out.println(car.getLicensePlate() + " -> ticket " + t1.ticketId + ", spot #" + t1.spot.getSpotNumber());
Vehicle truck = new Truck("TRK-9");
Ticket t2 = lot.enterVehicle(truck, makeTime(12, 0)); // noon, off-peak
System.out.println(truck.getLicensePlate() + " -> ticket " + t2.ticketId + ", spot #" + t2.spot.getSpotNumber());
System.out.println("Spots available now: " + manager.availableCount());
double fare1 = lot.leaveVehicle(t1, makeTime(10, 30)); // 90 min, peak
System.out.printf("%s parked 90 min in peak -> $%.2f%n", car.getLicensePlate(), fare1);
double fare2 = lot.leaveVehicle(t2, makeTime(12, 30)); // 30 min, off-peak
System.out.printf("%s parked 30 min off-peak -> $%.2f%n", truck.getLicensePlate(), fare2);
System.out.println("Spots available at close: " + manager.availableCount());
}
}Sample run — identical across all three languages:
Spots available at open: 5
CAR-1 -> ticket T001, spot #3
TRK-9 -> ticket T002, spot #5
Spots available now: 3
CAR-1 parked 90 min in peak -> $270.00
TRK-9 parked 30 min off-peak -> $90.00
Spots available at close: 5
In this section, we’ll cover common follow-up questions interviewers may ask about the parking lot system. These are important topics that interviewers might expect you to explore in detail.
The parking lot system is designed to support multiple parking spot types (e.g., CompactSpot, RegularSpot, OversizedSpot). However, there may be a need to introduce a new type, such as a handicapped parking spot, to accommodate specific requirements like accessibility. The challenge is to extend the system efficiently without modifying existing classes, adhering to the Open-Closed Principle (open for extension, closed for modification).
To achieve this, we can introduce a new HandicappedSpot class that implements the existing ParkingSpot interface. This approach ensures smooth integration with the system’s spot allocation and management logic, as ParkingManager already relies on the ParkingSpot interface for handling spots.
Below is the implementation of the HandicappedSpot class.
public class HandicappedSpot implements ParkingSpot {
private int spotNumber;
private Vehicle vehicle;
public HandicappedSpot(int spotNumber) {
this.spotNumber = spotNumber;
this.vehicle = null;
}
@Override
public int getSpotNumber() {
return spotNumber;
}
@Override
public boolean isAvailable() {
return vehicle == null;
}
@Override
public void occupy(Vehicle vehicle) {
if (isAvailable()) {
this.vehicle = vehicle;
} else {
// Spot is already occupied.
}
}
@Override
public void vacate() {
this.vehicle = null;
}
@Override
public VehicleSize getSize() {
return VehicleSize.MEDIUM;
}
}
The mapping we currently have is one-way: from Vehicle to ParkingSpot. This allows us to quickly find the parking spot assigned to a specific vehicle. But what if we want to find which vehicle is parked in a specific spot? Without a reverse mapping, we would need to search through all parking spots, which isn’t efficient. Can we do better?
We can enhance this by introducing another HashMap, called spotToVehicleMap, to track the reverse mapping from ParkingSpot to Vehicle.
With this approach, we use two HashMaps:
Below is the updated ParkingManager class.
public class ParkingManager {
private final Map<VehicleSize, List<ParkingSpot>> availableSpots;
private final Map<Vehicle, ParkingSpot> vehicleToSpotMap;
private final Map<ParkingSpot, Vehicle> spotToVehicleMap;
// Create Parking Manager based on a given map of available spots
public ParkingManager(Map<VehicleSize, List<ParkingSpot>> availableSpots) {
this.availableSpots = availableSpots;
this.vehicleToSpotMap = new HashMap<>();
this.spotToVehicleMap = new HashMap<>();
}
public ParkingSpot findSpotForVehicle(Vehicle vehicle) {
// No change in the method
}
public ParkingSpot parkVehicle(Vehicle vehicle) {
ParkingSpot spot = findSpotForVehicle(vehicle);
if (spot != null) {
spot.occupy(vehicle);
// Record bidirectional mapping
vehicleToSpotMap.put(vehicle, spot);
spotToVehicleMap.put(spot, vehicle);
// Remove the spot from the available list
availableSpots.get(spot.getSize()).remove(spot);
return spot; // Parking successful
}
return null; // No spot found for this vehicle
}
public void unparkVehicle(Vehicle vehicle) {
ParkingSpot spot = vehicleToSpotMap.remove(vehicle);
if (spot != null) {
spotToVehicleMap.remove(spot);
spot.vacate();
availableSpots.get(spot.getSize()).add(spot);
}
}
// Find vehicle's parking spot
public ParkingSpot findVehicleBySpot(Vehicle vehicle) {
return vehicleToSpotMap.get(vehicle);
}
// Find which vehicle is parked in a spot
public Vehicle findSpotByVehicle(ParkingSpot spot) {
return spotToVehicleMap.get(spot);
}
}
Implementation Benefits: The bidirectional mapping in ParkingManager enhances performance by adding a spotToVehicleMap alongside the vehicleToSpotMap, enabling O(1) lookups from a vehicle to a parking spot and vice versa. This eliminates the need to iterate through all parked vehicles to identify the one in a given parking spot. It’s especially efficient in large parking lots, where such iterations can be expensive.
With this enhancement explored, let’s summarize the key takeaways.
In this chapter, we gathered requirements for the Parking Lot system through detailed questions and answers. We identified the core objects involved, designed the class structure, and implemented the system's key components.
A key takeaway from this design is the value of modularity and clear separation of concerns. Each component, such as Vehicle, ParkingSpot, ParkingManager, and FareCalculator, handles a distinct responsibility, keeping the system maintainable and open to future enhancements.
Our design choices, like using ParkingLot as a facade to coordinate operations or employing the FareStrategy interface for flexible pricing, emphasize simplicity and adaptability. An alternative approach, such as embedding spot allocation and fare logic directly in ParkingLot, might reduce the number of classes but could complicate scalability by overloading a single class with multiple responsibilities. In an interview, reflecting on these decisions and articulating their benefits showcases your ability to balance trade-offs in object-oriented design.
Congratulations on getting this far! Now give yourself a pat on the back. Good job!
This section gives a quick overview of the design patterns used in this chapter. It’s helpful if you’re new to these patterns or need a refresher to understand the design choices better.
The Strategy pattern is a behavioral design pattern that defines a family of algorithms, encapsulates each one in a separate class, and allows their objects to be interchangeable.
In the parking lot design, we have used the Strategy pattern to encapsulate pricing rules in the FareStrategy interface (e.g., BaseFareStrategy, PeakHoursFareStrategy), allowing FareCalculator to switch between rules dynamically without altering its core logic.
To illustrate the Strategy pattern in another domain, the following example uses an e-commerce payment system.
Problem
Imagine you're developing an e-commerce application that offers various payment methods, such as credit cards, PayPal, and bank transfers. Initially, you might implement each payment method directly within the checkout process. However, as the application grows, this approach can lead to a monolithic design where the payment processing logic becomes tightly coupled with the checkout system. This tight coupling makes it challenging to add new payment methods or modify existing ones without changing the core checkout code, which increases the risk of introducing bugs and makes the system harder to maintain.
Solution
To address this issue, the Strategy design pattern can be employed. This pattern suggests encapsulating each payment algorithm in a separate class, known as a strategy, and making them interchangeable. The main application, referred to as the context, maintains a reference to a strategy object and delegates the payment processing to this object. This design allows the application to switch between different payment methods, without modifying the core checkout logic.
When to use
The Strategy design pattern is particularly useful in scenarios:
The Facade pattern is a structural design pattern that provides a simple interface to a complex subsystem, such as a library, framework, or set of classes. It simplifies how clients interact with the system by hiding its underlying complexity.
In the parking lot design, the Facade pattern is used in the ParkingLot class, which streamlines client interactions by managing tasks like vehicle entry, spot assignment, and fee calculation, delegating to subsystems such as ParkingManager and FareCalculator.
To illustrate the Facade pattern in another domain, the following example uses a home theater system.
Problem
Imagine you’re setting up a home theater system with multiple components, such as a DVD player, projector, sound system, and lights. To watch a movie, you must turn on each component, adjust settings, and synchronize them. This process is complex, requiring users to understand each component’s working. As the system grows, adding new devices (e.g., a streaming device) increases complexity, making it harder to use the system efficiently.
Solution
The Facade pattern addresses this by introducing a single interface, the facade, that encapsulates the subsystem’s complexity. For the home theater, a HomeTheaterFacade class could provide methods like watchMovie(), which internally manages all components (e.g., turning on the projector, setting the sound system). Clients interact only with the facade, which delegates tasks to the subsystem, simplifying usage.
When to use
The Facade design pattern is particularly useful in scenarios: