Design (LLD) a Parking Lot - Machine Coding Interview

Design (LLD) a Parking Lot - Machine Coding Interview

10-2017-ff-detail-1200x627.jpeg

Parking Lot

A parking lot or car park is a dedicated cleared area that is intended for parking vehicles. In most countries where cars are a major mode of transportation, parking lots are a feature of every city and suburban area. Shopping malls, sports stadiums, megachurches, and similar venues often feature parking lots over large areas.

Features

We will focus on the following set of requirements while designing the parking lot:

  1. The parking lot should have multiple floors where customers can park their cars.

  2. The parking lot should have multiple entry and exit points.

  3. Customers can collect a parking ticket from the entry points and can pay the parking fee at the exit points on their way out.

  4. Customers can pay the tickets at the automated exit panel or to the parking attendant.

  5. Customers can pay via both cash and credit cards.

  6. Customers should also be able to pay the parking fee at the customer’s info portal on each floor. If the customer has paid at the info portal, they don’t have to pay at the exit.

  7. The system should not allow more vehicles than the maximum capacity of the parking lot. If the parking is full, the system should be able to show a message at the entrance panel and on the parking display board on the ground floor.

  8. Each parking floor will have many parking spots. The system should support multiple types of parking spots such as Compact, Large, Handicapped, Motorcycle, etc.

  9. The Parking lot should have some parking spots specified for electric cars. These spots should have an electric panel through which customers can pay and charge their vehicles.

  10. The system should support parking for different types of vehicles like car, truck, van, motorcycle, etc.

  11. Each parking floor should have a display board showing any free parking spot for each spot type.

  12. The system should support a per-hour parking fee model. For example, customers have to pay $4 for the first hour, $3.5 for the second and third hours, and $2.5 for all the remaining hours.

Rough Solution (LLD-Machine Coding)

public enum VehicleType {
    CAR("Car"),
    TRUCK("Truck"),
    ELECTRIC("Electric Car"),
    VAN("Van"),
    MOTORBIKE("Motorbike");

    private String description;

    VehicleType(String description) {
        this.description = description;
    }

    public String getDescription() {
        return description;
    }

    // Check if a vehicle can fit in a given parking spot type
    public boolean canFitInSpot(ParkingSpotType spotType) {
        switch (this) {
            case TRUCK:
            case VAN:
                return spotType == ParkingSpotType.LARGE;
            case MOTORBIKE:
                return spotType == ParkingSpotType.MOTORBIKE;
            case CAR:
                return spotType == ParkingSpotType.COMPACT || spotType == ParkingSpotType.LARGE;
            case ELECTRIC:
                return spotType == ParkingSpotType.ELECTRIC || spotType == ParkingSpotType.COMPACT || spotType == ParkingSpotType.LARGE;
            default:
                return false;
        }
    }
}

// Enum for different parking spot types with additional helper methods
public enum ParkingSpotType {
    HANDICAPPED("Handicapped"),
    COMPACT("Compact"),
    LARGE("Large"),
    MOTORBIKE("Motorbike"),
    ELECTRIC("Electric Charging");

    private String displayName;

    ParkingSpotType(String displayName) {
        this.displayName = displayName;
    }

    public String getDisplayName() {
        return displayName;
    }

    // Check if the spot type is suitable for a specific vehicle type
    public boolean isSuitableFor(VehicleType vehicleType) {
        return vehicleType.canFitInSpot(this);
    }
}

// Enum for account status with methods for status validation
public enum AccountStatus {
    ACTIVE("Active"),
    BLOCKED("Blocked"),
    BANNED("Banned"),
    COMPROMISED("Compromised"),
    ARCHIVED("Archived"),
    UNKNOWN("Unknown");

    private String description;

    AccountStatus(String description) {
        this.description = description;
    }

    public String getDescription() {
        return description;
    }

    // Check if the account is in a good standing
    public boolean isAccountActive() {
        return this == ACTIVE;
    }
}

// Enum for parking ticket status with helper methods
public enum ParkingTicketStatus {
    ACTIVE("Ticket Active"),
    PAID("Ticket Paid"),
    LOST("Ticket Lost");

    private String description;

    ParkingTicketStatus(String description) {
        this.description = description;
    }

    public String getDescription() {
        return description;
    }

    // Check if the parking ticket is paid
    public boolean isPaid() {
        return this == PAID;
    }
}

// Singleton Pattern for ParkingLot
public class ParkingLot {
    private static ParkingLot instance;
    private String name;
    private Address address;
    private ParkingRate parkingRate;
    private Map<String, ParkingFloor> parkingFloors;
    private Map<String, EntrancePanel> entrancePanels;
    private Map<String, ExitPanel> exitPanels;
    private Map<String, ParkingTicket> activeTickets;

    private ParkingLot() {
        // Initialize parking lot details from the database
    }

    public static synchronized ParkingLot getInstance() {
        if (instance == null) {
            instance = new ParkingLot();
        }
        return instance;
    }

    public synchronized ParkingTicket getNewParkingTicket(Vehicle vehicle) throws ParkingFullException {
        if (this.isFull(vehicle.getType())) {
            throw new ParkingFullException("Parking full for vehicle type: " + vehicle.getType());
        }

        ParkingTicket ticket = new ParkingTicket();
        vehicle.assignTicket(ticket);
        ticket.saveInDB();
        incrementSpotCount(vehicle.getType());
        activeTickets.put(ticket.getTicketNumber(), ticket);
        return ticket;
    }

    public boolean isFull(VehicleType type) {
        // Check if parking is full for the specific vehicle type
        // Implementation for isFull logic based on vehicle type
    }

    private void incrementSpotCount(VehicleType type) {
        // Increment the spot count for the vehicle type
        // Implementation based on vehicle type
    }

    public void addParkingFloor(ParkingFloor floor) {
        // Add a new parking floor
        parkingFloors.put(floor.getName(), floor);
    }

    public void addEntrancePanel(EntrancePanel entrancePanel) {
        entrancePanels.put(entrancePanel.getId(), entrancePanel);
    }

    public void addExitPanel(ExitPanel exitPanel) {
        exitPanels.put(exitPanel.getId(), exitPanel);
    }
}

// Factory Method Pattern for creating parking spots
abstract class ParkingSpotFactory {
    public abstract ParkingSpot createParkingSpot();
}

class HandicappedSpotFactory extends ParkingSpotFactory {
    @Override
    public ParkingSpot createParkingSpot() {
        return new HandicappedSpot();
    }
}

class CompactSpotFactory extends ParkingSpotFactory {
    @Override
    public ParkingSpot createParkingSpot() {
        return new CompactSpot();
    }
}

class LargeSpotFactory extends ParkingSpotFactory {
    @Override
    public ParkingSpot createParkingSpot() {
        return new LargeSpot();
    }
}

// Factory Method for creating vehicles
abstract class VehicleFactory {
    public abstract Vehicle createVehicle();
}

class CarFactory extends VehicleFactory {
    @Override
    public Vehicle createVehicle() {
        return new Car();
    }
}

class TruckFactory extends VehicleFactory {
    @Override
    public Vehicle createVehicle() {
        return new Truck();
    }
}

// Observer Pattern for updating the ParkingDisplayBoard
interface Observer {
    void update(String message);
}

class ParkingDisplayBoard implements Observer {
    private String id;
    private ParkingSpot handicappedFreeSpot;
    private ParkingSpot compactFreeSpot;
    private ParkingSpot largeFreeSpot;
    private ParkingSpot motorbikeFreeSpot;
    private ParkingSpot electricFreeSpot;

    public void update(String message) {
        System.out.println("Display Board Updated: " + message);
        showEmptySpotNumber();
    }

    public void showEmptySpotNumber() {
        // Display the free spots on the board
        String message = "";
        message += handicappedFreeSpot.isFree() ? "Free Handicapped Spot: " + handicappedFreeSpot.getNumber() : "Handicapped full";
        message += "\n";
        message += compactFreeSpot.isFree() ? "Free Compact Spot: " + compactFreeSpot.getNumber() : "Compact full";
        // Continue for other spots...
        System.out.println(message);
    }
}

// Command Pattern for ticket processing
interface Command {
    void execute();
}

class ProcessTicketCommand implements Command {
    private ParkingAttendant attendant;
    private String ticketNumber;

    public ProcessTicketCommand(ParkingAttendant attendant, String ticketNumber) {
        this.attendant = attendant;
        this.ticketNumber = ticketNumber;
    }

    @Override
    public void execute() {
        attendant.processTicket(ticketNumber);
    }
}

// Strategy Pattern for payment processing
interface PaymentStrategy {
    void pay(ParkingTicket ticket);
}

class CashPaymentStrategy implements PaymentStrategy {
    @Override
    public void pay(ParkingTicket ticket) {
        System.out.println("Processing cash payment for ticket: " + ticket.getTicketNumber());
        ticket.updateStatus(ParkingTicketStatus.PAID);
    }
}

class CreditCardPaymentStrategy implements PaymentStrategy {
    @Override
    public void pay(ParkingTicket ticket) {
        System.out.println("Processing credit card payment for ticket: " + ticket.getTicketNumber());
        ticket.updateStatus(ParkingTicketStatus.PAID);
    }
}

// Template Method Pattern for vehicle parking
abstract class ParkingSpot {
    private String number;
    private boolean free = true;
    private Vehicle vehicle;
    private final ParkingSpotType type;

    public ParkingSpot(ParkingSpotType type) {
        this.type = type;
    }

    public boolean assignVehicle(Vehicle vehicle) {
        if (!this.isFree()) return false;
        this.vehicle = vehicle;
        free = false;
        return true;
    }

    public boolean removeVehicle() {
        this.vehicle = null;
        free = true;
        return true;
    }

    public boolean isFree() {
        return free;
    }

    public String getNumber() {
        return number;
    }

    public ParkingSpotType getType() {
        return type;
    }
}

class HandicappedSpot extends ParkingSpot {
    public HandicappedSpot() {
        super(ParkingSpotType.HANDICAPPED);
    }
}

class CompactSpot extends ParkingSpot {
    public CompactSpot() {
        super(ParkingSpotType.COMPACT);
    }
}

class LargeSpot extends ParkingSpot {
    public LargeSpot() {
        super(ParkingSpotType.LARGE);
    }
}

// Vehicle class remains unchanged
public class Car extends Vehicle {
    public Car() {
        super(VehicleType.CAR);
    }
}

public class Truck extends Vehicle {
    public Truck() {
        super(VehicleType.TRUCK);
    }
}

// Main class for testing
public class Main {
    public static void main(String[] args) {
        // Singleton Pattern: Get the parking lot instance
        ParkingLot parkingLot = ParkingLot.getInstance();

        // Create vehicles using Factory Method
        VehicleFactory carFactory = new CarFactory();
        Vehicle car = carFactory.createVehicle();

        // Create a parking ticket for the vehicle
        try {
            ParkingTicket ticket = parkingLot.getNewParkingTicket(car);
            System.out.println("Parking ticket issued: " + ticket.getTicketNumber());

            // Process the ticket using Command Pattern
            ParkingAttendant attendant = new ParkingAttendant();
            Command processTicketCommand = new ProcessTicketCommand(attendant, ticket.getTicketNumber());
            processTicketCommand.execute();

            // Pay for the ticket using Strategy Pattern
            PaymentStrategy paymentStrategy = new CashPaymentStrategy();
            paymentStrategy.pay(ticket);
        } catch (ParkingFullException e) {
            System.out.println(e.getMessage());
        }
    }
}

Did you find this article valuable?

Support Low Level Design (LLD) Coding by becoming a sponsor. Any amount is appreciated!