Requirements
Determine the different ways the system will be used. This includes main functions the system needs to perform and who will use it.
Primary functions:
- Parking spot assignment
- Assigns the most suitable parking spot based on car size and spot availability
- Spot availability checking
- Allows users to check if a spot is available, reserved, or occupied across multiple floors
- Fee calculation
- Calculates the fee to park based on parking duration
Actors:
- Driver
- Enters parking lot
- Gets spot assigned
- Parks and leaves
- Pays a fee based on how long they parked
Define Core Objects
Based on the requirements and use cases, identify the main objects of the system...
Objects:
- Parking lot
- Attributes
- List of floors
- Overall capacity
- Attributes
- Floor
- Attributes
- Floor id
- List of parking spots
- Floor capacity
- Attributes
- ParkingSpot
- Attributes
- Spot ID
- Size
- Status
- Attributes
- Vehicle
- Attributes
- Vehicle ID
- Size
- Attributes
- Ticket
- Attributes
- ID of parked vehicle
- ID of reserved parking spot
- Entry time
- Exit time
- Attributes
- Fee calculator
- Methods
- calculateFee(duration)
- Methods
- ParkingLotManager
- Methods
- create_ticket(vehicle_id, spot_id, entry_time, exit_time)
- check_availability(vehicle_id, spot_id, entry_time, exit_time)
- compute_fee(calculateFee)
- Methods
Analyze Relationships
Determine how these objects will interact with each other to fulfill the use cases...
- Parking lot contains multiple floors
- Composition
- Each floor is managed by the parking lot
- Floor contains multiple parking spots
- Composition
- Each parking spot is managed by the floor
- Vehicle is linked to ticket during parking
- Association
- A vehicle is assigned to ticket on creation
- Ticket is linked to parking spot
- Association
- Parking spot is available during the duration of the ticket
- Parking spot is assigned to ticket on creation
- ParkingLotManager uses FeeCalculator to compute the fee
- Utilization
- The system computes the fee using entry and exit time
- ParkingLotManager is linked to tickets
- Association
- Creates the ticket
- ParkingLotManager is linked to parking lot
- Association
- Manages and checks parking lot availability
Establish Hierarchy
Design inheritance trees where applicable to promote code reuse and polymorphism. This step involves identifying common attributes and behaviors that can be abstracted into parent classes...
Vehicle has a base class with size abstract method
- It is used to create different vehicles with different sizes.
Floor has a base class with vehicle type abstract method
- It is used to to create different floors with different allowed vehicle types
Design Patterns
Consider using design patterns (e.g., Factory, Singleton, Observer, Strategy) that fit the problem...
- Factory
- Car factory creates different types of vehicles
- Singleton
- A single instance of ParkingLotManager is created as a single source of truth
Define Class Members (write code)
Attributes: For each class, define the attributes (data) it will hold...
Methods: Define the methods (functions) that operate on the attributes. Ensure they align with the object's responsibilities and adhere to the principle of encapsulation.
class ParkingLot:
def __init__(self):
self.floors = [] # List[Floor]
self.total_capacity = 0 # int
def add_floor(self, floor):
self.floors.append(floor)
self.total_capacity += floor.capacity
class Floor:
def init(self, floor_id, capacity):
self.floor_id = floor_id # int or str
self.spots = [] # List[ParkingSpot]
self.capacity = capacity # int
def add_spot(self, spot):
self.spots.append(spot)
def get_available_spots(self, vehicle_size):
# Return list of available spots that match vehicle size
pass
class ParkingSpot:
def init(self, spot_id, size):
self.spot_id = spot_id # str
self.size = size # str ("small", "medium", "large")
self.status = "available" # "available", "occupied", "reserved"
def occupy(self):
self.status = "occupied"
def free_up(self):
self.status = "available"
class Vehicle:
def init(self, vehicle_id, size):
self.vehicle_id = vehicle_id # str
self.size = size # str ("small", "medium", "large")
class Ticket:
def init(self, ticket_id, vehicle_id, spot_id, entry_time):
self.ticket_id = ticket_id
self.vehicle_id = vehicle_id
self.spot_id = spot_id
self.entry_time = entry_time # datetime
self.exit_time = None # datetime
def close_ticket(self, exit_time):
self.exit_time = exit_time
def get_duration(self):
return self.exit_time - self.entry_time
class FeeCalculator:
def calculate_fee(self, entry_time, exit_time):
duration = exit_time - entry_time
return (duration.total_seconds() / 3600) * 2 # $2/hour
class ParkingLotManager:
def init(self, parking_lot):
self.parking_lot = parking_lot # ParkingLot
self.tickets = {} # Dict[ticket_id: Ticket]
def check_availability(self, vehicle):
# Iterate through floors and spots to find a match
pass
def create_ticket(self, vehicle, spot, entry_time):
ticket_id = generate_unique_id()
ticket = Ticket(ticket_id, vehicle.vehicle_id, spot.spot_id, entry_time)
spot.occupy()
self.tickets[ticket_id] = ticket
return ticket
def close_ticket(self, ticket_id, exit_time):
ticket = self.tickets.get(ticket_id)
ticket.close_ticket(exit_time)
fee = FeeCalculator().calculate_fee(ticket.entry_time, exit_time)
# Free up the spot
# return fee and ticket summary
return fee
Adhere to SOLID Guidelines
Check and explain whether your design adheres to solid principles (Ask interviewer what SOLID principle is if you can not recall it.)...
- I made FeeCalculator a separate class to follow Single Responsibility — it doesn’t need to know about vehicles or tickets.
- The vehicle factory exhibits open/closed principle by not modifying core behavior but extending car types.
Consider Scalability and Flexibility
Explain how your design can handle changes in scale and whether it would be easily to extend with new functionalities...
- The system is flexible and scalable due to its use of a factory pattern for vehicle creation. New vehicle types can be added without modifying the core logic of the vehicle class, adhering to the Open/Closed Principle. Additionally, the fee calculation logic follows the Single Responsibility Principle and is loosely coupled, making it easy to extend with new pricing strategies or fee rules without impacting other parts of the system.
- The core objects of the system are decoupled from each other, making it easy to scale horizontally. We can then allocate resources only to components that need to be scaled by making them individual services. The interoperability of the system is well defined to ensure no issues in decoupling.
Create/Explain your diagram(s)
Try creating a class, flow, state and/or sequence diagram using the diagramming tool. Mermaid flow diagrams can be used to represent system use cases. You can ask the interviewer bot to create a starter diagram if unfamiliar with the tool. Briefly explain your diagrams if necessary...
Future improvements
Critically examine your design for any flaws or areas for future improvement...