Parking Lot LLD Design | Need Feedback

I have designed below system for the parking lot:

  • Parking lot can have multiple entrance and exits
  • At entrance ticket needs to be created
  • there are 2 type of vehicle for Car and Bike
  • and exit gate payment would be done

Note: please ignore logic and exit class ( this should be abstract but for now I have not kept it like that)

I would really appreciate you feedback and suggestions. It would be really helpful if I get to know the weak points as well as strong ones:

image

export abstract class Entrance {
  private managerFactory: ParkingSlotManagerfactory;
  constructor(managerfactory: ParkingSlotManagerfactory) {
    this.managerFactory = managerfactory;
  }

  findParkingandGenerateTicket(
    vehicle: Vehicle,
    charge: PARKING_PRICE_STRATEGY = PARKING_PRICE_STRATEGY.HOURLY
  ) {
    const manager = this.managerFactory.getManager(
      vehicle.getVehicleType()
    );
    const parkingSlot = manager.findParkingSlot();
    parkingSlot?.parkVehicle(vehicle);

    return new Ticket(vehicle, parkingSlot);
  }
}

export class MainEntrance extends Entrance {
  constructor(managerFactory: ParkingSlotManagerfactory) {
    super(managerFactory);
  }
}

export class Exit {
  private managerFactory: ParkingSlotManagerfactory;

  constructor(managerFactory: ParkingSlotManagerfactory) {
    this.managerFactory = managerFactory;
  }

  calculatePrice(ticket: Ticket) {
    const manager = this.managerFactory.getManager(
      ticket.vehicle.getVehicleType()
    );
    const price = CalculatePrice.calculatePrice(ticket);
  }
}

export class HourlyPriceStrategy implements PriceStrategy {
  calculatePrice(ticket: Ticket): number {
    const hoursParked =
      (new Date().getTime() - ticket.entryTime.getTime()) /
      3600000;
    console.log(hoursParked);
    console.log(
      Math.ceil(hoursParked) * ticket.parkingSlot.price
    );
    return 15;
  }
}

export class MinutePriceStrategy implements PriceStrategy {
  calculatePrice(ticket: Ticket): number {
    return 10;
  }
}

export interface PriceStrategy {
  calculatePrice(ticket: Ticket): number;
}

export abstract class ParkingSlot {
  id: number;
  isOccupied: boolean;
  isOccupiedBy: Vehicle | null;
  slotType: ParkingSlotType;
  price: PARKINGPRICE;

  constructor(
    id: number,
    slotType: ParkingSlotType,
    price: PARKINGPRICE
  ) {
    this.id = id;
    this.isOccupied = false;
    this.slotType = slotType;
    this.isOccupiedBy = null;
    this.price = price;
  }

  parkVehicle(vehicle: Vehicle) {
    this.isOccupiedBy = vehicle;
    this.isOccupied = true;
  }
}

export class CarSlot extends ParkingSlot {
  constructor(id: number) {
    super(id, ParkingSlotType.CAR, PARKINGPRICE.CAR);
  }
}

export class BikeSlot extends ParkingSlot {
  constructor(id: number) {
    super(id, ParkingSlotType.BIKE, PARKINGPRICE.BIKE);
  }
}

export abstract class ParkingSlotManager {
  slotList: Array<ParkingSlot>;

  constructor(slotLists: Array<ParkingSlot>) {
    this.slotList = slotLists;
  }
  findParkingSlot(): ParkingSlot {
    if (this.slotList.length === 0) {
      throw new Error("No available parking slot");
    }
    return this.slotList.pop()!;
  }

  addParkingSlot(parkingSlot: ParkingSlot) {
    this.slotList.push(parkingSlot);
  }
  removeParkingSlot() {}
  parkVehicle() {}
  unparkVehicle() {}
}

export class CarParkingManager extends ParkingSlotManager {
  constructor(slotList: ParkingSlot[]) {
    super(slotList);
  }
}

export class BikeParkingManager extends ParkingSlotManager {
  constructor(slotList: Array<ParkingSlot>) {
    super(slotList);
  }
}

export class CalculatePrice {
  static calculatePrice(ticket: Ticket) {
    const strategy =
      PakringPriceStrategyFactory.getPriceStrategy(
        ticket.chargeBy
      );
    strategy.calculatePrice(ticket);
  }
}

export abstract class Vehicle {
  private vehicleNumber: string;
  private vehicleType: VehicleType;

  constructor(
    vehicleNumber: string,
    vehicleType: VehicleType
  ) {
    this.vehicleType = vehicleType;
    this.vehicleNumber = vehicleNumber;
  }

  getVehicleNumber(): string {
    return this.vehicleNumber;
  }

  getVehicleType(): VehicleType {
    return this.vehicleType;
  }

  setVehicleType(vehicle: VehicleType) {
    this.vehicleType = vehicle;
  }

  setVehicleNumber(vehicleNumber: string) {
    this.vehicleNumber = vehicleNumber;
  }
}

export class Car extends Vehicle {
  constructor(vehicleNumber: string) {
    super(vehicleNumber, VehicleType.Car);
  }
}

export class Bike extends Vehicle {
  constructor(vehicleNummber: string) {
    super(vehicleNummber, VehicleType.Bike);
  }
}

export class Ticket {
  id: number;
  vehicle: Vehicle;
  entryTime: Date;
  exitTime: Date | undefined;
  status: TicketStatus;
  parkingSlot: ParkingSlot;
  chargeBy: PARKING_PRICE_STRATEGY;

  constructor(
    vehicle: Vehicle,
    parking: ParkingSlot,
    chargeBy: PARKING_PRICE_STRATEGY = PARKING_PRICE_STRATEGY.HOURLY
  ) {
    this.id = Math.floor(Math.random() * 1000000);
    this.vehicle = vehicle;
    this.entryTime = new Date();
    this.status = TicketStatus.PENDING;
    this.parkingSlot = parking;
    this.chargeBy = chargeBy;
  }
}

export class ParkingSlotManagerfactory {
  private carManager: CarParkingManager;
  private bikeManager: BikeParkingManager;

  constructor(
    carManager: CarParkingManager,
    bikeManager: BikeParkingManager
  ) {
    this.carManager = carManager;
    this.bikeManager = bikeManager;
  }

  getManager(vehicleType: VehicleType): ParkingSlotManager {
    switch (vehicleType) {
      case VehicleType.Car:
        return this.carManager;
      case VehicleType.Bike:
        return this.bikeManager;
      default:
        throw new Error("Invalid vehicle type");
    }
  }
}

export class PakringPriceStrategyFactory {
  static getPriceStrategy(
    priceStrategy: PARKING_PRICE_STRATEGY
  ) {
    switch (priceStrategy) {
      case PARKING_PRICE_STRATEGY.HOURLY:
        return new HourlyPriceStrategy();
      case PARKING_PRICE_STRATEGY.MINUTE:
        return new MinutePriceStrategy();
      default:
        throw new Error(
          `Invalid price strategy: ${priceStrategy}`
        );
    }
  }
}
export enum VehicleType {
  Car = "car",
  Bike = "bike",
}

export enum TicketStatus {
  PENDING = "pending",
  PAID = "paid",
  CANCELLED = "cancelled",
}
export enum PARKING_PRICE_STRATEGY {
  HOURLY = "Hour",
  MINUTE = "minute",
}
export enum ParkingSlotType {
  CAR = "Car",
  BIKE = "Bike",
}
export enum PARKINGPRICE {
  CAR = 50,
  BIKE = 30,
}

// main class 

let carSlotList = [];
let bikeSlotList = [];

for (let i = 0; i < 10; i++) {
  carSlotList.push(new CarSlot(i + 1));
}

for (let i = 0; i < 10; i++) {
  bikeSlotList.push(new BikeSlot(i + 1));
}

const carParkingManager = new CarParkingManager(
  carSlotList
);
const bikeParkingManager = new BikeParkingManager(
  bikeSlotList
);

const managerFactory = new ParkingSlotManagerfactory(
  carParkingManager,
  bikeParkingManager
);

const mainEntrance = new MainEntrance(managerFactory);
const exit = new Exit(managerFactory);

const car = new Car("DL6s437676");

console.log(car.getVehicleType());
const ticket =
  mainEntrance.findParkingandGenerateTicket(car);

console.log(ticket);

exit.calculatePrice(ticket);
Comments (5)