Requirements
Functional Requirements:
- Manage vehicle parking across multiple floors.
- Parking spot assignment based on vehicle size.
- Spot availability checking.
- If spots available, assign a random spot.
- If spots full, tell user there are no spots available and to join a waitlist.
- Create waitlist queue.
- Create Ticket creation and ticket processing service.
- Fee calculation based on parking duration. Fee's are calculated as follows:
- 0-1 hour: $1
- 2-3 hours: $3
- 3-5 hours: $6
- 5-9 hours: $9
- 9+ hours: $24 (assume full 24 hours)
Non-Functional Requirements:
- Handle ticket assignment race conditions - ie: 2 drivers both want spot A, both can't be assigned the same spot. Solution: 1 synchronized method for inserting driver spot permission and 1 synchronized method for deleting driver spot permission. Use a Concurrent Hashmap to handle fast reads and updates from users.
- Handle extensibility for operations like payment methods through the adapter design pattern.
Core Objects & Relationships
Based on the requirements and use cases, identify the main objects of the system and analyze how they interact and relate to each other...
Here's a Class Diagram illustrating the relationships of the core objects and too each other:
classDiagram
class VehicleType{
<<enumeration>>
MOTORCYCLE
COMPACT
REGULAR
LARGE
EV
}
class ParkingSpotType{
<<enumeration>>
MOTORCYCLE
COMPACT
REGULAR
LARGE
EV
}
class TicketStatus{
<<enumeration>>
OPEN
CLOSED
LOST
}
class ParkingGarage {
+ConcurrentHashMap<int, ParkingFloor> floors
+TicketService ticketService
+CreateTicket() Ticket
+CloseTicket() Ticket
}
class TicketService {
-List<Tickets> tickets
-Queue<Vehicle> waitList
+CreateTicket(floors) int
+CloseTicket(ticketId) int
+alertSpotOpen() void
}
class Vehicle {
+getMake/Model/Year/VehicleType() String/Int
+setMake/Model/year/VehicleType() void
+String make
+String model
+int year
+VehicleType vehicleType
+Driver user
+String liscensePlateNum
}
class ParkingFloor {
+getAvailableSpots() availableSpots
+updateParkingSpot(parkingSpot) bool
+int floorNum
+ParkingSpots[] spots
+Queue<ParkingSpots> availableSpots
}
class ParkingSpot {
+canFitVehicle(Vehicle v) bool
-ParkingSpotType parkingType
-int spotNum
-Vehicle Vehicle
-Boolean isAssigned
}
class Driver {
+method() void
+String fn
+String ln
+int age
+String email
}
class Ticket {
+Date check_in
+Date check_out
+TicketStatus status
+Int fee
+ParkingSpot spot
+feeCalculation() Float
}
class PricingStrategy{
<<interface>>
+calculate(Ticket ticket) BigDecimal
}
class DurationBasedPricingStrategy{
+calculate(Ticket ticket) BigDecimal
}
class VehicleTypePricingStrategy{
+calculate(Ticket ticket) BigDecimal
}
class EventPricingStrategy{
+calculate(Ticket ticket) BigDecimal
}
ParkingGarage *-- TicketService
TicketService "1" --> "*" Ticket
TicketService "1" --> "0..*" Vehicle
Vehicle "1" --> "1" Driver
ParkingGarage "1" --> "0..*" ParkingFloor
ParkingFloor "1" --> "0..*" ParkingSpot
ParkingSpot "1" --> "0..1" Vehicle
PricingStrategy <|-- DurationBasedPricingStrategy : implements
PricingStrategy <|-- VehicleTypePricingStrategy : implements
PricingStrategy <|-- EventPricingStrategy : implements
APIs & Class Members
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.
Deep Dive
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...
Design Tradeoffs:
- I chose a simple object model with ParkingGarage, ParkingFloor, ParkingSpot, TicketService, Ticket, vehicle and driver because it maps closely to the real-world domain.
- The biggest tradeoff is simplicity vs flexibility
- For example, fee calculation could be a simple method on Ticket, but I separated it into a PricingStrategy. For the current rule set, that may be slightly overengineered, but it makes the design easier to extend later for vehicle-based pricing, event pricing, weekend pricing, or dynamic pricing.
- Another tradeoff is concurrency granularity. Synchronizing the entire TicketService is simple and safe, but limits throughput. A better scalable design is to make individual ParkingSpot assignment atomic so multiple floors/spots can be processed concurrently.
Solid Principles:
- Single Responsibility Principle
- ParkingGarage manages floors and delegates ticket operations.
- TicketService handles ticket creation and closing.
- ParkingSpot manages spot availability and assignment.
- PricingStrategy handles fee calculation.
- One improvement: TicketService currently does both ticket lifecycle management and spot-finding logic. In a larger system, I would extract spot search into a separate ParkingSpotAllocationService.
- Open/Closed Principle:
- yes, such as pricing. Pricing is behind a pricingStrategy interface making VehicleTypePricingStrategy, EventPricingStrategy, etc, without chaning TicketService.
- Liskov Substitution Principle:
- Any implementation of PricingStrategy can replace another as long as it returns a valid fee for a ticket.
- Interface Segregation Principle:
Yes. PricingStrategy is small and focused: BigDecimal calculate(Ticket ticket);
- No class is forced to implement methods it does not need.
Future improvement:
- The biggest improvements I would make are:
- Extract spot allocation into a separate service or strategy.
- Add a repository layer.
- Improve concurrency with spot-level locking instead of service-level locking.
- Add payment processing.
- Replace hardcoded vehicle compatibility rules with a policy class.