Assign parking spots based on vehicle size, allowing motorcycles to park in small spots, cars in medium spots, and trucks in large spots, with the option to park in larger spots if needed.
Based on the requirements and use cases, identify the main objects of the system and analyze how they interact and relate to each other...
The core objects here are Vehicle, ParkingGarage, ParkingLevel, ParkingSpot, and ParkingTicket.
ParkingGarage:
ParkingLevel:
ParkingSpot:
Vehicle:
PricingStrategy:
ParkingTicket:
For each class, define the attributes (data) it will hold and the methods (functions) that operate on the attributes. Ensure they align with the object's responsibilities and adhere to the principle of encapsulation. Write your code in the code editor below.
# Define your classes here
from abc import abstractmethod, ABC
from enum import Enum
class VehicleType(Enum):
MOTORCYCLE = 1
CAR = 2
TRUCK = 3
class SpotType(Enum):
SMALL = 1
MEDIUM = 2
LARGE = 3
class Vehicle:
def __init__(self, license_plate: str, vehicle_type: VehicleType):
self.license_plate = license_plate
self.vehicle_type = vehicle_type
def get_license_plate(self):
return self.license_plate
def get_spot_type(self):
return self.vehicle_type.ordinal()
class ParkingSpot:
def __init__(self, spot_type: SpotType):
self.id = uuid4().hex
self.vehicle = None
self.spot_type = spot_type
def can_park(self, vehicle: Vehicle):
return self.vehicle is None and self.spot_type.ordinal() >= self.vehicle.get_spot_type().ordinal()
def park(self, vehicle: Vehicle):
if self.can_park(vehicle):
self.vehicle = vehicle
return True
return False
def get_id(self):
return self.id
def clear(self):
self.vehicle = None
class ParkingLevel:
def __init__(self, spots: list[ParkingSpot]):
self.spots = spots
def park(self, vehicle: Vehicle):
for i in range(len(self.spots)):
if self.spots[i].park(vehicle):
return self.spots[i].get_id()
def clear_spot(self, spot_id):
for spot in spots:
if spot.get_id() == spot_id:
spot.clear()
break
class PricingStrategy(ABC):
def __init__(self):
pass
@abstractmethod
def calculate_price(self, ticket: Ticket):
pass
class ParkingGarage:
def __init__(self, levels: list[ParkingLevel], pricing_strategy: PricingStrategy):
self.levels = levels
self.pricing_strategy = pricing_strategy
def park_vehicle(self, vehicle: Vehicle, timestamp: float) -> Ticket | None:
for i in range(len(levels)):
spot_id = self.levels[i].park(vehicle)
if spot_id is not None:
return Ticket(start_time=timestamp, level_number=i, spot_id=spot_id)
print("No spots available!")
return None
def handle_exit(self, ticket: Ticket):
self.levels[ticket.level_number].clear_spot(ticket.spot_id)
price = self.pricing_strategy.calculate(ticket)
return price
@dataclass
class Ticket:
start_time: float
level_number: int
spot_id: str
Explain design tradeoffs you considered. Check and explain whether your design adheres to SOLID principles. Explain how your design can handle changes in scale and whether it would be easy to extend with new functionalities. Identify areas for future improvement...
The data flow is as described below:
A user requests to park in a lot with a vehicle. This will then search each individual parking level to find a spot. Going down deeper, each level checks each spot to see if there is a fitting spot. If there is a spot, its id is noted, and returned back. In total, the parking floor, the spot id, the license plate, and vehicle type is noted on the Ticket.
When a user wants to exit, they simply pass the ticket back into the system, and the pricing engine calculates the price of the ticket and returns it to the user.
S: It follows the single responsibility principle. Each class handles one specific job or data domain.
O: Open for extension, closed for modification. If there needs to be more types of vehicle types and spot types added, the enums provide a good standardized way to include that. You simply would just add a new type to the enums. However, if there were special types of spots that were handicapped, this might break down. This is where inheritance or some kind of wrapper might be useful for these special types of spots.
L: Liskovs substitution Princple - You can substitue a subclass of each class into the methods
I: Not really relevant here
D: Dependency Inversion Principle - This is implemented well, notably in the PricingStrategy. The parking garage doesn't need to know about the specific implementation, it just depends a standardized interface.
Scalability:
Usually parking lots have at most in the thousands of spots available, so optimizing with something like a hashmap for faster lookups may not be too useful. However, if the system were to extend across multiple parking lots and the system had to query more data, then querying by index could be helpful here.
For future improvement, relying on the enums might not be the most scalable for multiple types. For something more complex, then inheritance for parking spots could become a viable solution.
Concurrency:
To make this safe for multiple cars trying to park at once, we can implement a lock per spot, since there won't be too many spots in this case. When someone tries to park, they acquire the lock, and then park.
However, lock free data structures such as ConcurrentHashMaps would be better for higher scale.