LLD Parking Lot

Designing a Parking Lot System involves the use of various design patterns to structure the system in a way that is flexible, scalable, and easy to maintain. In this case, the Parking Lot System is built to manage parking spots, vehicles, parking tickets, and payment modes.

Requirment 1

  • Design a basic Parking lot

Solution

Identify key objects/classes in the system.

  • Parking Spot: Space for parking
    • Attributes:
      • spot_id
      • is_available
      • assigned_vehicle
  • Parking Lot : A parking lot has many parking spot
    • Attributes
      • slots
    • Method
    • add_spot()
    • find_available_spot()
    • park_vehicle()
    • remove_vehicle()
  • Vehicle : Vehicle will park
    • Attributes
      • license_plate
class Vehicle:
    def __init__(self,license_plate):
        self.license_plate = license_plate

class ParkingSpot:
    def __init__(self,spot_id):
        self.spot_id=spot_id
        self.is_available = True
        self.assigned_vehicle = None

    def slot_available(self):
        return self.is_available

    def book_slot(self,vehicle):
        self.is_available  = False
        self. assigned_vehicle = vehicle.license_plate

    def unbook_slot(self):
        self.is_available = True
        self. assigned_vehicle = None

class ParkingLot:

    def __init__(self):
        self.slots = []

    def add_spot(self,spot):
        self.slots.append(spot)

    def find_available_spot(self):
        for slot in self.slots:
            if slot.slot_available():
                return slot
        return None

    def park_vehicle(self,vehicle):
        slot = self.find_available_spot()

        if slot:
            slot.book_slot(vehicle)
            print(f"Vehicle {vehicle.license_plate} Parked at {slot.spot_id}")
            return slot.spot_id

        print("No Space")
        return None


    def remove_vehicle(self,vehicle):
        for slot in self.slots:
            if slot.license_plate ==vehicle.license_plate:
                slot.unbook_slot()
                print(f"Vehicle {vehicle.license_plate} unparked from {slot.spot_id}")


# Vehicles
car1 = Vehicle("MH1234")
car2 = Vehicle("DL987")
car3 = Vehicle("KA456")

#Adding Parking Spot in parking lot
slot = ParkingSpot("a1")
lot = ParkingLot()
lot.add_spot(slot)

#Parking
lot.park_vehicle(car1)
lot.park_vehicle(car2)
lot.remove_vehicle(car1)
lot.park_vehicle(car2)
lot.park_vehicle(car3)


'''
Output 

Vehicle MH1234 Parked at a1
No Space
Vehicle MH1234 unparked from a1
Vehicle DL987 Parked at a1
No Space
'''
Python

+----------------+           +----------------+           +------------------+
|   ParkingLot   |<>---------|  ParkingSpot   |<>---------|     Vehicle      |
+----------------+           +----------------+           +------------------+
| - name: str    |           | - spot_id: str |           | - license_plate: str |
| - spots: list  |           | - spot_type: str           +------------------+ 
+----------------+           | - is_free: bool|           
| +add_spot()    |           | - assigned_vehicle: Vehicle|
| +find_spot()   |           +----------------+           
| +park_vehicle()|           | +park_vehicle()|
| +remove_vehicle()|         | +remove_vehicle()|
+----------------+           +----------------+
Python

Key Points

Use getter/setter methods, instead of directly accessing variables like slot.license_plate.

if slot.license_plate == vehicle.license_plate: # bad Practice
if slot.license_plate() == vehicle.license_plate(): #Good

Python

Use str() for better printing. Example In Vehicle Class

def __str__(self):
    return f"Vehicle({self.license_plate})"
Python

Now print(vehicle) will show Vehicle(MH1234).

Requirment 2

  • Support for various type of vehicle

Solution

To support different type of vehicle, we must have different type of parking spot as well

Identify key objects/classes in the system.

Vehicle : Vehicle will park

  • Attributes
    • license_plate

One class for each vehicle type

ParkingSpot

  • Attributes
    • parking_type
class Vehicle:
    def __init__(self,license_plate,vehicle_type):
        self.license_plate = license_plate
        self.vehicle_type = vehicle_type

    def get_license_plate(self):
        return self.license_plate

    def get_vehicle_type(self):
        return self.vehicle_type

    def __str__(self):
        return f"{self.vehicle_type} - {self.license_plate}"


class CarVehicle(Vehicle):
    def __init__(self, license_plate):
        super().__init__(license_plate, "car")


class BikeVehicle(Vehicle):
    def __init__(self, license_plate):
        super().__init__(license_plate, "bike")


class ParkingSpot:
    def __init__(self,spot_id,parking_type):
        self.spot_id=spot_id
        self.is_available = True
        self.assigned_vehicle = None
        self.parking_type = parking_type

    def get_parking_type(self):
        return self.parking_type

    def get_license_plate(self):
        return self.assigned_vehicle

    def get_spot_id(self):
        return self.spot_id

    def slot_available(self):
        return self.is_available

    def book_slot(self,vehicle):
        self.is_available  = False
        self.assigned_vehicle = vehicle.get_license_plate()

    def unbook_slot(self):
        self.is_available = True
        self. assigned_vehicle = None

class ParkingLot:

    def __init__(self):
        self.slots = []

    def add_spot(self,spot):
        self.slots.append(spot)

    def find_available_spot(self,vehicle_type):
        for slot in self.slots:
            if slot.slot_available() and slot.get_parking_type() == vehicle_type:
                return slot
        return None

    def park_vehicle(self,vehicle):
        slot = self.find_available_spot(vehicle.get_vehicle_type())

        if slot:
            slot.book_slot(vehicle)
            print(f"{vehicle} Parked at {slot.get_spot_id()}")
            return slot.get_spot_id()

        print(f"No Space  for {vehicle}")
        return None

    def remove_vehicle(self,vehicle):
        for slot in self.slots:
            if slot.get_license_plate() == vehicle.get_license_plate():
                slot.unbook_slot()
                print(f"{vehicle} unparked from {slot.get_spot_id()}")



car1 = CarVehicle("MH1234")
car2 = CarVehicle("DL987")
car3 = CarVehicle("UP563")

bike1 = BikeVehicle("KA456")
bike2 = BikeVehicle("TN347")

#Adding Parking Spot in parking lot
slot1 = ParkingSpot("c1","car")
slot2 = ParkingSpot("c2","car")

slot3 = ParkingSpot("b1","bike")


lot = ParkingLot()
lot.add_spot(slot1)
lot.add_spot(slot2)
lot.add_spot(slot3)


#Parking
lot.park_vehicle(car1)
lot.park_vehicle(car2)
lot.remove_vehicle(car1)
lot.park_vehicle(car2)

lot.park_vehicle(bike1)
lot.park_vehicle(bike2)
lot.park_vehicle(car3)


'''
OutPut
car - MH1234 Parked at c1
car - DL987 Parked at c2
car - MH1234 unparked from c1
car - DL987 Parked at c1
bike - KA456 Parked at b1
No Space  for bike - TN347
No Space  for car - UP563
'''
Python

Future

  • Can add to avoid double parking
  • self.assigned_vehicle = vehicle.get_license_plate()
    • Which only stores the license plate. This can work, but it’s more powerful if you store the whole vehicle object, so you can access vehicle_type, etc., later if needed.
def book_slot(self, vehicle):
    self.is_available = False
    self.assigned_vehicle = vehicle  # store the object
Python

If we have multiple values, it wise to pass object only

Factory Method

Problem Without Factory

In any real application, you will get string from user input(or API) like “car” or “bike”

To handle that, we will create the right object based on that string.

if vehicle_type == "car":
    vehicle = CarVehicle(license_plate)
elif vehicle_type == "bike":
    vehicle = BikeVehicle(license_plate)
Python

This will work, but now you’re repeating this logic everywhere you need to create a vehicle.

And if you add a new type (like TruckVehicle) — you’ll have to update every place that has this if-elif logic.

What the Factory Pattern Does?

It centralizes that object creation logic into a single class (the Factory).

You just write:

vehicle = VehicleFactory.create_vehicle("car", "MH1234")
Python

And the factory decides which class to use internally. If you later add TruckVehicle, you update only the factory — not every part of your codebase.

class VehicleFactory:
    @staticmethod
    def create_vehicle(vehicle_type, license_plate):
        vehicle_type = vehicle_type.lower()
        if vehicle_type == "car":
            return CarVehicle(license_plate)
        elif vehicle_type == "bike":
            return BikeVehicle(license_plate)
        # Future vehicle types can go here
        else:
            raise ValueError(f"Unsupported vehicle type: {vehicle_type}")
Python

Requirment 3

  • Give Payment Option

Solution

For collecting payment. We need to collection duration the vehicle is parked

Identify key objects/classes in the system.

  • Parking Ticket
    • Attributes:
      • vehicle_plate
      • entry_time
      • exit_time
  • Payment
    • Attributes
      • payment_method
      • duration

Why NOT to Store Entry/Exit Time in ParkingSpot?

Storing entry/exit time here violates the Single Responsibility Principle (SRP) — because now the spot is handling temporal and transactional logic, which belongs to something like a ParkingTicket.

Per hour rate for different vehicles

We can do it in various different ways

  • Maintain a rate card
  • For simpicity, I will put in Vehicle subclass

Should self.active_tickets go in the Vehicle class?

No, Because active_tickets is not the responsibility of the vehicle. It’s part of the system’s management of vehicles, not the vehicle itself.

So, active_tickets should go in ParkingLot

from abc import ABC,abstractmethod
from datetime import datetime

class Vehicle:
    def __init__(self,license_plate,vehicle_type):
        self.license_plate = license_plate
        self.vehicle_type = vehicle_type

    def get_license_plate(self):
        return self.license_plate

    def get_vehicle_type(self):
        return self.vehicle_type

    @abstractmethod
    def get_rate_per_hour(self):
        pass

    def __str__(self):
        return f"{self.vehicle_type} - {self.license_plate}"


class CarVehicle(Vehicle):
    def __init__(self, license_plate):
        super().__init__(license_plate, "car")

    def get_rate_per_hour(self):
        return 10

class BikeVehicle(Vehicle):
    def __init__(self, license_plate):
        super().__init__(license_plate, "bike")

    def get_rate_per_hour(self):
        return 5

class VehicleFactory:
    @staticmethod
    def create_vehicle(vehicle_type, license_plate):
        vehicle_type = vehicle_type.lower()
        if vehicle_type == "car":
            return CarVehicle(license_plate)
        elif vehicle_type == "bike":
            return BikeVehicle(license_plate)
        # Future vehicle types can go here
        else:
            raise ValueError(f"Unsupported vehicle type: {vehicle_type}")

class ParkingSpot:
    def __init__(self,spot_id,parking_type):
        self.spot_id=spot_id
        self.is_available = True
        self.assigned_vehicle = None
        self.parking_type = parking_type

    def get_parking_type(self):
        return self.parking_type

    def get_license_plate(self):
        return self.assigned_vehicle

    def get_spot_id(self):
        return self.spot_id

    def slot_available(self):
        return self.is_available

    def book_slot(self,vehicle):
        self.is_available  = False
        self.assigned_vehicle = vehicle.get_license_plate()

    def unbook_slot(self):
        self.is_available = True
        self. assigned_vehicle = None

class ParkingLot:

    def __init__(self):
        self.slots = []
        self.active_tickets = {} # dict, so that we can reach it by license

    def add_spot(self,spot):
        self.slots.append(spot)

    def find_available_spot(self,vehicle_type):
        for slot in self.slots:
            if slot.slot_available() and slot.get_parking_type() == vehicle_type:
                return slot
        return None

    def park_vehicle(self,vehicle):
        slot = self.find_available_spot(vehicle.get_vehicle_type())

        if slot:
            slot.book_slot(vehicle)
            print(f"{vehicle} Parked at {slot.get_spot_id()}")
            ticket = ParkingTicket().get_ticket(vehicle)

            self.active_tickets[vehicle.get_license_plate()] = ticket

            return slot.get_spot_id()

        print(f"No Space  for {vehicle}")
        return None

    def remove_vehicle(self,vehicle):
        for slot in self.slots:
            if slot.get_license_plate() == vehicle.get_license_plate():
                slot.unbook_slot()

                ticket =  self.active_tickets[vehicle.get_license_plate()]
                ticket.mark_exit()

                fee = ticket.calculate_fees()

                print(f"{vehicle} unparked from {slot.get_spot_id()}, fee Rs{fee}")


                del self.active_tickets[vehicle.get_license_plate()]


class ParkingTicket:
    def __init__(self):
        self.entry_time = None
        self.exit_time = None
        self.vehicle = None

    def get_ticket(self,vehicle):
        self.entry_time = datetime.now()
        self.vehicle = vehicle
        return self


    def mark_exit(self):
        self.exit_time = datetime.now()


    def calculate_fees(self):
        duration = self.exit_time - self.entry_time
        hours = max(1, duration.total_seconds() // 3600)
        rate = self.vehicle.get_rate_per_hour()
        return hours * rate



lot = ParkingLot()
# for simplicity, let add spot dynamically
for i in range(5):
    lot.add_spot(ParkingSpot(f"c{i}","car"))

for i in range(5):
    lot.add_spot(ParkingSpot(f"b{i}","bike"))


car1 = VehicleFactory.create_vehicle("car", "MH1234")
car2 = VehicleFactory.create_vehicle("car", "DL987")
car3 = VehicleFactory.create_vehicle("car", "UP563")

bike1 = VehicleFactory.create_vehicle("bike", "KA456")
bike2 = VehicleFactory.create_vehicle("bike", "TN347")



#Parking
lot.park_vehicle(car1)
lot.park_vehicle(car2)
lot.remove_vehicle(car1)
lot.park_vehicle(car2)

lot.park_vehicle(bike1)
lot.park_vehicle(bike2)
lot.park_vehicle(car3)

lot.remove_vehicle(bike1)


'''
Output
car - MH1234 Parked at c0
car - DL987 Parked at c1
car - MH1234 unparked from c0, fee Rs10
car - DL987 Parked at c0
bike - KA456 Parked at b0
bike - TN347 Parked at b1
car - UP563 Parked at c2
bike - KA456 unparked from b0, fee Rs5
'''
Python

Requirment 4

To support different modes of payment

Identify key objects/classes in the system.

  • Payment
    • Attributes
      • Fee
      • Payment time
      • Payment status
  • ParkingTicket
    • Attributes
      • make_payment

Why make_payment() is in the ParkingTicket class (not Payment)?

Because, The ParkingTicket knows:

  • The entry and exit time
  • The vehicle (which has the rate)
  • How long the vehicle parked
  • Therefore, it can calculate the fee
from abc import ABC,abstractmethod
from datetime import datetime

class Vehicle:
    def __init__(self,license_plate,vehicle_type):
        self.license_plate = license_plate
        self.vehicle_type = vehicle_type

    def get_license_plate(self):
        return self.license_plate

    def get_vehicle_type(self):
        return self.vehicle_type

    @abstractmethod
    def get_rate_per_hour(self):
        pass

    def __str__(self):
        return f"{self.vehicle_type} - {self.license_plate}"


class CarVehicle(Vehicle):
    def __init__(self, license_plate):
        super().__init__(license_plate, "car")

    def get_rate_per_hour(self):
        return 10

class BikeVehicle(Vehicle):
    def __init__(self, license_plate):
        super().__init__(license_plate, "bike")

    def get_rate_per_hour(self):
        return 5

class VehicleFactory:
    @staticmethod
    def create_vehicle(vehicle_type, license_plate):
        vehicle_type = vehicle_type.lower()
        if vehicle_type == "car":
            return CarVehicle(license_plate)
        elif vehicle_type == "bike":
            return BikeVehicle(license_plate)
        # Future vehicle types can go here
        else:
            raise ValueError(f"Unsupported vehicle type: {vehicle_type}")

class ParkingSpot:
    def __init__(self,spot_id,parking_type):
        self.spot_id=spot_id
        self.is_available = True
        self.assigned_vehicle = None
        self.parking_type = parking_type

    def get_parking_type(self):
        return self.parking_type

    def get_license_plate(self):
        return self.assigned_vehicle

    def get_spot_id(self):
        return self.spot_id

    def slot_available(self):
        return self.is_available

    def book_slot(self,vehicle):
        self.is_available  = False
        self.assigned_vehicle = vehicle.get_license_plate()

    def unbook_slot(self):
        self.is_available = True
        self. assigned_vehicle = None

class ParkingLot:

    def __init__(self):
        self.slots = []
        self.active_tickets = {} # dict, so that we can reach it by license

    def add_spot(self,spot):
        self.slots.append(spot)

    def find_available_spot(self,vehicle_type):
        for slot in self.slots:
            if slot.slot_available() and slot.get_parking_type() == vehicle_type:
                return slot
        return None

    def park_vehicle(self,vehicle):
        slot = self.find_available_spot(vehicle.get_vehicle_type())

        if slot:
            slot.book_slot(vehicle)
            print(f"{vehicle} Parked at {slot.get_spot_id()}")
            ticket = ParkingTicket().get_ticket(vehicle)

            self.active_tickets[vehicle.get_license_plate()] = ticket

            return slot.get_spot_id()

        print(f"No Space  for {vehicle}")
        return None

    def remove_vehicle(self,vehicle,payment_mode):
        for slot in self.slots:
            if slot.get_license_plate() == vehicle.get_license_plate():
                slot.unbook_slot()

                ticket =  self.active_tickets[vehicle.get_license_plate()]
                ticket.mark_exit()

                ticket.calculate_fees()
                print(f"{vehicle} unparked from {slot.get_spot_id()}")

                ticket.make_payment(payment_mode)

                del self.active_tickets[vehicle.get_license_plate()]



class ParkingTicket:
    def __init__(self):
        self.entry_time = None
        self.exit_time = None
        self.vehicle = None
        self.fee = 0.0

    def get_ticket(self,vehicle):
        self.entry_time = datetime.now()
        self.vehicle = vehicle
        return self


    def mark_exit(self):
        self.exit_time = datetime.now()


    def calculate_fees(self):
        duration = self.exit_time - self.entry_time
        hours = max(1, duration.total_seconds() // 3600)
        rate = self.vehicle.get_rate_per_hour()
        self.fee= hours * rate


    def make_payment(self, mode):
        payment_method = PaymentFactory.get_payment_handler(mode,self.fee)
        return payment_method.process()


class Payment(ABC):

    def __init__(self,amount):
        self.amount  = amount


    @abstractmethod
    def process(self):
        pass

class UPIPayment(Payment):
    def process(self):
        print(f"Processing UPI Payment of {self.amount} with UPI ID")

class CardPayment(Payment):
    def process(self):
        print(f"Processing Card Payment of {self.amount} with CVV check")


class CashPayment(Payment):
    def process(self):
        print(f"Accepting cash {self.amount}")


class PaymentFactory:
    @staticmethod
    def get_payment_handler(mode, amount):
        mode = mode.lower()
        if mode == "upi":
            return UPIPayment(amount)
        elif mode == "card":
            return CardPayment(amount)
        elif mode == "cash":
            return CashPayment(amount)
        else:
            raise ValueError(f"Unsupported payment mode: {mode}")


lot = ParkingLot()
# for simplicity, let add spot dynamically
for i in range(5):
    lot.add_spot(ParkingSpot(f"c{i}","car"))

for i in range(5):
    lot.add_spot(ParkingSpot(f"b{i}","bike"))


car1 = VehicleFactory.create_vehicle("car", "MH1234")
car2 = VehicleFactory.create_vehicle("car", "DL987")
car3 = VehicleFactory.create_vehicle("car", "UP563")

bike1 = VehicleFactory.create_vehicle("bike", "KA456")
bike2 = VehicleFactory.create_vehicle("bike", "TN347")





#Parking
lot.park_vehicle(car1)
lot.park_vehicle(car2)
lot.remove_vehicle(car1,"upi")
lot.park_vehicle(car2)

lot.park_vehicle(bike1)
lot.park_vehicle(bike2)
lot.park_vehicle(car3)

lot.remove_vehicle(bike1,"cash")


'''
Output
car - MH1234 Parked at c0
car - DL987 Parked at c1
car - MH1234 unparked from c0
Processing UPI Payment of 10 with UPI ID
car - DL987 Parked at c0
bike - KA456 Parked at b0
bike - TN347 Parked at b1
car - UP563 Parked at c2
bike - KA456 unparked from b0
Accepting cash 5

'''
Python

Future

We add multiple level parking

Design Pattern Used

Factory Method Pattern

Where?

  • VehicleFactory.create_vehicle(…)
  • PaymentFactory.get_payment_handler(…)

Purpose:

  • Provides a way to create objects (e.g., CarVehicle, BikeVehicle, UPIPayment, CashPayment, etc.) without specifying the exact class name directly in your logic.

Why it’s good:

  • Promotes scalability: new vehicle or payment types can be added easily.
  • Follows Open/Closed Principle: open for extension, closed for modification.

Strategy Pattern

Where?

  • Payment modes: UPIPayment, CardPayment, CashPayment (all implement a common process() interface).

Purpose:

  • Allows the algorithm (payment processing) to vary independently from the clients using it.

Why it’s cool here:

  • You can switch payment mode at runtime without changing the core logic.

Template Method Pattern (minor)

Where?

  • Vehicle is an abstract class that defines the interface (get_rate_per_hour) to be implemented by subclasses like CarVehicle, BikeVehicle.

Purpose:

  • Defines the skeleton of an algorithm in a base class and lets subclasses override specific steps.

Single Responsibility Principle (SRP)

Not a pattern, but a SOLID principle you are following well.

Examples:

  • ParkingLot manages slots and vehicle flow.
  • Vehicle handles vehicle details.
  • Payment handles payments.
  • ParkingTicket manages timing and fees.

Template Pattern Vs Stategy Pattern

Strategy Pattern: The Strategy Pattern is used to define a family of algorithms (or strategies), encapsulate each one, and make them interchangeable.

  • The strategy pattern allows the client to choose the appropriate algorithm at runtime without modifying the classes that implement it. This pattern is often used when you have different variants of an algorithm or operation and you want to make them interchangeable.
  • The different types of payment (UPI, Card, Cash) implement the common process method in their own way.
  • The strategy pattern allows an algorithm (in this case, the payment processing) to be selected at runtime, with each specific implementation encapsulated in a separate class.
class Payment(ABC):

    def __init__(self,amount):
        self.amount  = amount

    @abstractmethod
    def process(self):
        pass

class UPIPayment(Payment):
    def process(self):
        print(f"Processing UPI Payment of {self.amount} with UPI ID")

class CardPayment(Payment):
    def process(self):
        print(f"Processing Card Payment of {self.amount} with CVV check")

class CashPayment(Payment):
    def process(self):
        print(f"Accepting cash {self.amount}")
Python

In above code, you have different types of payment (UPI, Card, and Cash). Each type of payment has its own distinct implementation for processing a payment. Here’s how the Strategy Pattern works for you:

  • The Payment class defines a common interface for all payment types. It includes an abstract method process(), but does not define how the payment should be processed.
  • The concrete subclasses (UPIPayment, CardPayment, CashPayment) provide the specific details on how the payment is processed.

The client can select the payment strategy at runtime, like so:

def make_payment(payment: Payment):
    payment.process()  # Client calls the process method on any Payment type

payment1 = UPIPayment(100)
payment2 = CardPayment(200)
payment3 = CashPayment(300)

make_payment(payment1)  # Uses UPI payment logic
make_payment(payment2)  # Uses Card payment logic
make_payment(payment3)  # Uses Cash payment logic
Python

class Payment(ABC):
    def __init__(self, amount):
        self.amount = amount
    
    def make_payment(self):
        self.validate_payment()
        self.process()
        self.confirm_payment()

    def validate_payment(self):
        print(f"Validating payment of {self.amount}")

    @abstractmethod
    def process(self):
        pass

    def confirm_payment(self):
        print(f"Payment of {self.amount} confirmed!")

class UPIPayment(Payment):
    def process(self):
        print(f"Processing UPI Payment of {self.amount} with UPI ID")

class CardPayment(Payment):
    def process(self):
        print(f"Processing Card Payment of {self.amount} with CVV check")
Python

Let make it Template

Template Method Pattern

The Template Method Pattern defines the skeleton of an algorithm in a method, deferring some steps to subclasses. The main idea is that the base class provides a template (a fixed structure) for the overall algorithm, while subclasses can provide specific implementations for certain steps in that algorithm.

If you were to use the Template Method Pattern, the base class (Payment) would define a common structure or sequence of steps that every payment needs to go through (e.g., validate the payment, process it, confirm the payment). The concrete classes (UPIPayment, CardPayment, CashPayment) would then only implement the specifics of how each payment type processes the payment.

class Payment(ABC):
    def __init__(self, amount):
        self.amount = amount

    def make_payment(self):
        self.validate_payment()  # Step 1: Common step
        self.process()            # Step 2: Specific to subclass
        self.confirm_payment()    # Step 3: Common step

    def validate_payment(self):
        print(f"Validating payment of {self.amount}")

    @abstractmethod
    def process(self):
        pass

    def confirm_payment(self):
        print(f"Payment of {self.amount} confirmed!")

class UPIPayment(Payment):
    def process(self):
        print(f"Processing UPI Payment of {self.amount} with UPI ID")

class CardPayment(Payment):
    def process(self):
        print(f"Processing Card Payment of {self.amount} with CVV check")
Python

  • The make_payment() method in the base class (Payment) defines the overall structure of a payment transaction.
    • It first validates the payment (common step).
    • Then, it calls the process() method (which will vary by payment type, implemented by subclasses).
    • Finally, it confirms the payment (common step).
  • The subclasses (UPIPayment, CardPayment) only need to provide the specific details of how the payment is processed.

In this case, you are defining the overall flow of the algorithm in the base class, and only customizing the specific steps (like how the payment is processed) in the subclasses.

Client Code

def make_payment(payment: Payment):
    payment.make_payment()  # This calls the template method

# Create different payment objects
upi_payment = UPIPayment(100)  # UPI payment of 100
card_payment = CardPayment(200)  # Card payment of 200
cash_payment = CashPayment(300)  # Cash payment of 300

# Process each payment using the common template
make_payment(upi_payment)  # Validating payment of 100, Processing UPI Payment of 100 with UPI ID, Payment of 100 confirmed!
make_payment(card_payment)  # Validating payment of 200, Processing Card Payment of 200 with CVV check, Payment of 200 confirmed!
make_payment(cash_payment)  # Validating payment of 300, Accepting cash 300, Payment of 300 confirmed!
Python

Which Pattern Should You Use?

Strategy Pattern: Use this pattern if the payment methods (UPI, Card, Cash, etc.) are fundamentally different in how they work, and you don’t need to enforce a strict control flow for how the payment is processed.

Template Method Pattern: Use this pattern if the overall structure of the payment process (e.g., validate, process, confirm) is consistent, but you need different subclasses to implement how each specific step is carried out.

The Strategy Pattern is used when you want to define a family of interchangeable algorithms (e.g., different payment methods), while the Template Method Pattern is used when you want to define a common algorithm structure with customizable steps (e.g., payment processing with a fixed sequence).

Conclusion

The Parking Lot System incorporates various design patterns to manage complexity and enhance flexibility. The Factory Pattern allows for the dynamic creation of different vehicle types (e.g., Car, Bike), while the Strategy Pattern allows different payment methods to be handled seamlessly with a shared process() interface. These patterns ensure that the system can scale to include more vehicle types and payment methods without requiring major code changes. By using these patterns, the design maintains clear separation of concerns, promotes extensibility, and allows the parking lot system to handle diverse operational requirements efficiently. This approach aligns with modern object-oriented design principles, resulting in a robust and adaptable system.

Resoures

2 thoughts on “LLD Parking Lot”

Leave a Comment