Design Example: The Parking Lot
Designing a Parking Lot is a classic OOD problem. Let's apply our 4-step framework to build a robust, scalable system.
Step 1: Requirements Gathering
In an interview, you must clarify the scope. Here are the key questions and clarified requirements:
- Capacity: How many floors and spots?
- Clarified: Multiple floors, each with varying capacities.
- Vehicle Types: What vehicles should we support?
- Clarified: Motorbikes, Cars, and Trucks.
- Spot Types: Do spots have sizes?
- Clarified: Small (for bikes), Medium (for cars), and Large (for trucks).
- Payment: How is the fee calculated?
- Clarified: Hourly rate based on vehicle type.
Core Use Cases:
- A driver can get a ticket and enter the parking lot.
- The system finds the nearest available spot for the vehicle type.
- A driver can pay the fee and exit.
- Admin can add/remove floors or spots.
Step 2: Identify Core Objects
- ParkingLot: A Singleton managing the entire system.
- ParkingFloor: Manages a collection of spots.
- ParkingSpot: An abstract base for
SmallSpot,MediumSpot, andLargeSpot. - Vehicle: An abstract base for
Motorbike,Car, andTruck. - Ticket: Represents the entry/exit record.
- PaymentManager: Handles fee calculation and processing.
Step 3: Design Class Diagram
Step 4: Implementation in TypeScript
enum VehicleType {
MOTORBIKE,
CAR,
TRUCK
}
// 1. Vehicle Abstraction
abstract class Vehicle {
constructor(public id: string, public type: VehicleType) {}
}
class Car extends Vehicle {
constructor(id: string) { super(id, VehicleType.CAR); }
}
// 2. Parking Spot Abstraction
abstract class ParkingSpot {
protected occupied: boolean = false;
constructor(public id: string, public type: VehicleType) {}
isAvailable(): boolean { return !this.occupied; }
park(): void { this.occupied = true; }
unpark(): void { this.occupied = false; }
}
class CompactSpot extends ParkingSpot {
constructor(id: string) { super(id, VehicleType.CAR); }
}
// 3. Parking Floor
class ParkingFloor {
constructor(public id: string, private spots: ParkingSpot[]) {}
findAvailableSpot(type: VehicleType): ParkingSpot | undefined {
return this.spots.find(s => s.isAvailable() && s.type === type);
}
}
// 4. Parking Lot (Singleton)
class ParkingLot {
private static instance: ParkingLot;
private floors: ParkingFloor[] = [];
private constructor() {}
static getInstance(): ParkingLot {
if (!this.instance) this.instance = new ParkingLot();
return this.instance;
}
addFloor(floor: ParkingFloor) { this.floors.push(floor); }
parkVehicle(vehicle: Vehicle): boolean {
for (const floor of this.floors) {
const spot = floor.findAvailableSpot(vehicle.type);
if (spot) {
spot.park();
console.log(`Vehicle ${vehicle.id} parked at spot ${spot.id}`);
return true;
}
}
return false;
}
}Advanced Design Patterns
To make the design even more robust, we can apply:
1. Strategy Pattern (For Pricing)
Instead of hardcoding pricing in the PaymentManager, use a Strategy pattern to handle different pricing rules (e.g., Weekday vs. Weekend rates).
interface PricingStrategy {
calculatePrice(hours: number): number;
}
class FlatPricing implements PricingStrategy {
calculatePrice(hours: number) { return hours * 20; }
}2. Facade Pattern (For User Interaction)
Provide a simple interface for the user to interact with the complex internal system.
class ParkingFacade {
private lot = ParkingLot.getInstance();
requestEntry(licensePlate: string, type: VehicleType) {
const vehicle = new Car(licensePlate); // Simplified
return this.lot.parkVehicle(vehicle);
}
}Wrap Up
Designing a Parking Lot tests your ability to handle object relationships, use design patterns, and think about scalability (e.g., adding Electric Chargers or Valet service).
[!TIP] Always start with Requirement Clarification before writing a single line of code. This demonstrates that you are a thoughtful engineer who understands the problem first.