Designing a parking lot using object-oriented principles involves breaking down the system into classes, attributes, and methods that reflect real-world entities. Key components like vehicles and parking spaces can be modeled as objects, while interactions such as parking can be handled through methods. This approach promotes modularity, reusability, and maintainability, making the system easy to extend and manage.

Assumptions
For our purposes right now, we'll make the following assumptions. We made these specific assumptions to add a bit of complexity to the problem without adding too much.
- The parking lot has multiple levels. Each level has multiple rows of spots.
- The parking lot can park motorcycles, cars, and buses.
- The parking lot has motorcycle spots, compact spots, and large spots.
- A motorcycle can park in any spot.
- A car can park in either a single compact spot or a single large spot.
- A bus can park in five large spots that are consecutive and within the same row. It cannot park in small spots
So we have created an abstract class Vehicle, from which Car, Bus, and Motorcycle inherit.
Object-Oriented Design
We begin by creating the necessary classes and ensuring each class has a clear, single responsibility. Let's break down the design with a focus on how each class and method interacts.
1. Vehicle Class
The Vehicle class defines common attributes and behaviors for all types of vehicles. It will serve as a base class for more specific vehicle types like Bus, Car, and Motorcycle.
#include <string>
#include <iostream>
enum class VehicleSize { Small, Large };
class Vehicle {
protected:
std::string licensePlate;
int spotsNeeded;
VehicleSize size;
public:
Vehicle(std::string licensePlate, VehicleSize size) {
this->licensePlate = licensePlate;
this->size = size;
this->spotsNeeded = (size == VehicleSize::Large)? 5 : 1;
}
int getSpotsNeeded() {
return spotsNeeded;
}
VehicleSize getSize() {
return size;
}
std::string getLicensePlate() {
return licensePlate;
}
virtual bool canFitInSpot(ParkingSpot spot) = 0;
};
public abstract class Vehicle {
protected String licensePlate;
protected int spotsNeeded;
protected VehicleSize size;
public Vehicle(String licensePlate, VehicleSize size) {
this.licensePlate = licensePlate;
this.size = size;
this.spotsNeeded = (size == VehicleSize.Large) ? 5 : 1;
}
public int getSpotsNeeded() {
return spotsNeeded;
}
public VehicleSize getSize() {
return size;
}
public String getLicensePlate() {
return licensePlate;
}
public abstract boolean canFitInSpot(ParkingSpot spot);
}
from enum import Enum
class VehicleSize(Enum):
Small = 1
Large = 2
class Vehicle:
def __init__(self, license_plate: str, size: VehicleSize):
self.license_plate = license_plate
self.size = size
self.spots_needed = 5 if size == VehicleSize.Large else 1
def get_spots_needed(self):
return self.spots_needed
def get_size(self):
return self.size
def get_license_plate(self):
return self.license_plate
def can_fit_in_spot(self, spot):
raise NotImplementedError
var VehicleSize;
(function (VehicleSize) {
VehicleSize[VehicleSize["Small"] = 0] = "Small";
VehicleSize[VehicleSize["Large"] = 1] = "Large";
})(VehicleSize || (VehicleSize = {}));
var Vehicle = /** @class */ (function () {
function Vehicle(licensePlate, size) {
this.licensePlate = licensePlate;
this.size = size;
this.spotsNeeded = size === VehicleSize.Large? 5 : 1;
}
Vehicle.prototype.getSpotsNeeded = function () {
return this.spotsNeeded;
};
Vehicle.prototype.getSize = function () {
return this.size;
};
Vehicle.prototype.getLicensePlate = function () {
return this.licensePlate;
};
Vehicle.prototype.canFitInSpot = function (spot) {
throw new Error('Method not implemented.');
};
return Vehicle;
}());
2. Concrete Vehicle Classes
Bus: A bus requires 5 consecutive large spots.
#include <string>
#include "Vehicle.h"
#include "VehicleSize.h"
#include "ParkingSpot.h"
class Bus : public Vehicle {
public:
Bus(std::string licensePlate) : Vehicle(licensePlate, VehicleSize::Large) {}
bool canFitInSpot(ParkingSpot spot) {
return spot.getSpotSize() == VehicleSize::Large;
}
};
public class Bus extends Vehicle {
public Bus(String licensePlate) {
super(licensePlate, VehicleSize.Large);
}
public boolean canFitInSpot(ParkingSpot spot) {
return spot.getSpotSize() == VehicleSize.Large;
}
}
from enum import Enum
class VehicleSize(Enum):
Large = 3
class ParkingSpot:
def __init__(self, size):
self.size = size
def get_spot_size(self):
return self.size
class Vehicle:
def __init__(self, license_plate, size):
self.license_plate = license_plate
self.size = size
class Bus(Vehicle):
def __init__(self, license_plate):
super().__init__(license_plate, VehicleSize.Large)
def can_fit_in_spot(self, spot):
return spot.get_spot_size() == VehicleSize.Large
class Vehicle {
constructor(licensePlate, size) {
this.licensePlate = licensePlate;
this.size = size;
}
}
class VehicleSize {
static Large = 'Large';
}
class ParkingSpot {
constructor(size) {
this.size = size;
}
getSpotSize() {
return this.size;
}
}
class Bus extends Vehicle {
constructor(licensePlate) {
super(licensePlate, VehicleSize.Large);
}
canFitInSpot(spot) {
return spot.getSpotSize() === VehicleSize.Large;
}
}
Car: A car can park in either compact or large spots.
#include <string>
#include "Vehicle.h"
#include "ParkingSpot.h"
class Car : public Vehicle {
public:
Car(std::string licensePlate) : Vehicle(licensePlate, VehicleSize::Compact) {}
bool canFitInSpot(ParkingSpot spot) {
return spot.getSpotSize() == VehicleSize::Compact || spot.getSpotSize() == VehicleSize::Large;
}
};
public class Car extends Vehicle {
public Car(String licensePlate) {
super(licensePlate, VehicleSize.Compact);
}
public boolean canFitInSpot(ParkingSpot spot) {
return spot.getSpotSize() == VehicleSize.Compact || spot.getSpotSize() == VehicleSize.Large;
}
}
from enum import Enum
class VehicleSize(Enum):
Compact = 1
Large = 2
class Vehicle:
def __init__(self, license_plate, size):
self.license_plate = license_plate
self.size = size
class ParkingSpot:
def __init__(self, size):
self.size = size
def get_spot_size(self):
return self.size
class Car(Vehicle):
def __init__(self, license_plate):
super().__init__(license_plate, VehicleSize.Compact)
def can_fit_in_spot(self, spot):
return spot.get_spot_size() == VehicleSize.Compact or spot.get_spot_size() == VehicleSize.Large
class VehicleSize {
static Compact = 'Compact';
static Large = 'Large';
}
class Vehicle {
constructor(licensePlate, size) {
this.licensePlate = licensePlate;
this.size = size;
}
}
class ParkingSpot {
constructor(size) {
this.size = size;
}
getSpotSize() {
return this.size;
}
}
class Car extends Vehicle {
constructor(licensePlate) {
super(licensePlate, VehicleSize.Compact);
}
canFitInSpot(spot) {
return spot.getSpotSize() === VehicleSize.Compact || spot.getSpotSize() === VehicleSize.Large;
}
}
Motorcycle: A motorcycle can park in any spot
#include <string>
class Vehicle {
public:
Vehicle(std::string licensePlate, VehicleSize size) {}
};
class Motorcycle : public Vehicle {
public:
Motorcycle(std::string licensePlate) : Vehicle(licensePlate, VehicleSize::Motorcycle) {}
bool canFitInSpot(ParkingSpot spot) {
return true; // Can park in any spot
}
};
public class Motorcycle extends Vehicle {
public Motorcycle(String licensePlate) {
super(licensePlate, VehicleSize.Motorcycle);
}
public boolean canFitInSpot(ParkingSpot spot) {
return true; // Can park in any spot
}
}
class Vehicle:
def __init__(self, license_plate, size):
self.license_plate = license_plate
self.size = size
class Motorcycle(Vehicle):
def __init__(self, license_plate):
super().__init__(license_plate, 'Motorcycle')
def can_fit_in_spot(self, spot):
return True # Can park in any spot
class Vehicle {
constructor(licensePlate, size) {
this.licensePlate = licensePlate;
this.size = size;
}
}
class Motorcycle extends Vehicle {
constructor(licensePlate) {
super(licensePlate, 'Motorcycle');
}
canFitInSpot(spot) {
return true; // Can park in any spot
}
}
3. ParkingSpot Class
The ParkingSpot class represents an individual parking spot in the parking lot. It is responsible for managing its availability and verifying whether a specific vehicle can fit in the spot.
- We could have implemented this by having classes for LargeSpot, CompactSpot, and MotorcycleSpot which inherit from ParkingSpot, but this is probably overkilled.
- The spots probably do not have different behaviors, other than their sizes.
#include <iostream>
#include <string>
#include <vector>
enum class VehicleSize { Compact, Large, Motorcycle };
class Vehicle {
public:
Vehicle(VehicleSize size) : size(size) {}
VehicleSize getSize() const { return size; }
bool canFitInSpot(const VehicleSize& spotSize) const { return size <= spotSize; }
private:
VehicleSize size;
};
class Level {
public:
// Level implementation
};
class ParkingSpot {
private:
Vehicle* vehicle;
VehicleSize spotSize;
int row;
int spotNumber;
Level* level;
public:
ParkingSpot(Level* level, int row, int spotNumber, VehicleSize spotSize);
bool isAvailable() const;
bool canFitVehicle(const Vehicle& vehicle) const;
void parkVehicle(Vehicle* vehicle);
void removeVehicle();
VehicleSize getSpotSize() const;
int getRow() const;
int getSpotNumber() const;
};
ParkingSpot::ParkingSpot(Level* level, int row, int spotNumber, VehicleSize spotSize) : vehicle(nullptr), spotSize(spotSize), row(row), spotNumber(spotNumber), level(level) {}
bool ParkingSpot::isAvailable() const { return vehicle == nullptr; }
bool ParkingSpot::canFitVehicle(const Vehicle& vehicle) const { return isAvailable() && vehicle.canFitInSpot(spotSize); }
void ParkingSpot::parkVehicle(Vehicle* vehicle) { if (canFitVehicle(*vehicle)) this->vehicle = vehicle; }
void ParkingSpot::removeVehicle() { this->vehicle = nullptr; }
VehicleSize ParkingSpot::getSpotSize() const { return spotSize; }
int ParkingSpot::getRow() const { return row; }
int ParkingSpot::getSpotNumber() const { return spotNumber; }
public class ParkingSpot {
private Vehicle vehicle;
private VehicleSize spotSize;
private int row;
private int spotNumber;
private Level level;
public ParkingSpot(Level level, int row, int spotNumber, VehicleSize spotSize) {
this.level = level;
this.row = row;
this.spotNumber = spotNumber;
this.spotSize = spotSize;
this.vehicle = null;
}
public boolean isAvailable() {
return vehicle == null;
}
public boolean canFitVehicle(Vehicle vehicle) {
return isAvailable() && vehicle.canFitInSpot(this);
}
public void parkVehicle(Vehicle vehicle) {
if (canFitVehicle(vehicle)) {
this.vehicle = vehicle;
}
}
public void removeVehicle() {
this.vehicle = null;
}
public VehicleSize getSpotSize() {
return spotSize;
}
public int getRow() {
return row;
}
public int getSpotNumber() {
return spotNumber;
}
}
from enum import Enum
class VehicleSize(Enum):
Compact = 1
Large = 2
Motorcycle = 3
class Vehicle:
def __init__(self, size):
self.size = size
def can_fit_in_spot(self, spot_size):
return self.size <= spot_size
class Level:
pass
class ParkingSpot:
def __init__(self, level, row, spot_number, spot_size):
self.vehicle = None
self.spot_size = spot_size
self.row = row
self.spot_number = spot_number
self.level = level
def is_available(self):
return self.vehicle is None
def can_fit_vehicle(self, vehicle):
return self.is_available() and vehicle.can_fit_in_spot(self.spot_size)
def park_vehicle(self, vehicle):
if self.can_fit_vehicle(vehicle):
self.vehicle = vehicle
def remove_vehicle(self):
self.vehicle = None
def get_spot_size(self):
return self.spot_size
def get_row(self):
return self.row
def get_spot_number(self):
return self.spot_number
javascript
const VehicleSize = Object.freeze({
Compact: 'Compact',
Large: 'Large',
Motorcycle: 'Motorcycle'
});
class Vehicle {
constructor(size) {
this.size = size;
}
canFitInSpot(spotSize) {
return this.size === spotSize || this.size === VehicleSize.Motorcycle;
}
}
class Level {
// Level implementation
}
class ParkingSpot {
constructor(level, row, spotNumber, spotSize) {
this.vehicle = null;
this.spotSize = spotSize;
this.row = row;
this.spotNumber = spotNumber;
this.level = level;
}
isAvailable() {
return this.vehicle === null;
}
canFitVehicle(vehicle) {
return this.isAvailable() && vehicle.canFitInSpot(this.spotSize);
}
parkVehicle(vehicle) {
if (this.canFitVehicle(vehicle)) {
this.vehicle = vehicle;
}
}
removeVehicle() {
this.vehicle = null;
}
getSpotSize() {
return this.spotSize;
}
getRow() {
return this.row;
}
getSpotNumber() {
return this.spotNumber;
}
}
4. ParkingLevel Class
The Level class represents a level in the parking lot. It manages a collection of parking spots and provides methods to park and remove vehicles.
#include <vector>
#include "ParkingSpot.h"
#include "Vehicle.h"
class Level {
private:
int levelNumber;
std::vector<ParkingSpot> spots;
public:
Level(int levelNumber, int numSpots) : levelNumber(levelNumber), spots(numSpots) {}
bool parkVehicle(Vehicle vehicle) {
for (ParkingSpot& spot : spots) {
if (spot.canFitVehicle(vehicle)) {
spot.parkVehicle(vehicle);
return true;
}
}
return false;
}
bool removeVehicle(Vehicle vehicle) {
for (ParkingSpot& spot : spots) {
if (spot.isOccupied() && spot.getVehicle() == vehicle) {
spot.removeVehicle();
return true;
}
}
return false;
}
};
public class Level {
private int levelNumber;
private ParkingSpot[] spots;
public Level(int levelNumber, int numSpots) {
this.levelNumber = levelNumber;
this.spots = new ParkingSpot[numSpots];
}
public boolean parkVehicle(Vehicle vehicle) {
for (ParkingSpot spot : spots) {
if (spot.canFitVehicle(vehicle)) {
spot.parkVehicle(vehicle);
return true;
}
}
return false;
}
public boolean removeVehicle(Vehicle vehicle) {
for (ParkingSpot spot : spots) {
if (spot.isOccupied() && spot.getVehicle().equals(vehicle)) {
spot.removeVehicle();
return true;
}
}
return false;
}
}
from ParkingSpot import ParkingSpot
from Vehicle import Vehicle
class Level:
def __init__(self, level_number, num_spots):
self.level_number = level_number
self.spots = [ParkingSpot() for _ in range(num_spots)]
def park_vehicle(self, vehicle):
for spot in self.spots:
if spot.can_fit_vehicle(vehicle):
spot.park_vehicle(vehicle)
return True
return False
def remove_vehicle(self, vehicle):
for spot in self.spots:
if spot.is_occupied() and spot.get_vehicle() == vehicle:
spot.remove_vehicle()
return True
return False
class Level {
constructor(levelNumber, numSpots) {
this.levelNumber = levelNumber;
this.spots = Array.from({ length: numSpots }, () => new ParkingSpot());
}
parkVehicle(vehicle) {
for (let spot of this.spots) {
if (spot.canFitVehicle(vehicle)) {
spot.parkVehicle(vehicle);
return true;
}
}
return false;
}
removeVehicle(vehicle) {
for (let spot of this.spots) {
if (spot.isOccupied() && spot.getVehicle() === vehicle) {
spot.removeVehicle();
return true;
}
}
return false;
}
}
5. ParkingLot Class
The ParkingLot class represents the entire parking lot. It manages multiple levels and provides methods to park and remove vehicles from the parking lot.
#include <iostream>
#include <vector>
class Vehicle {};
class Level {
int levelNumber;
std::vector<Vehicle*> spots;
public:
Level(int level, int numSpots) : levelNumber(level), spots(numSpots) {}
bool parkVehicle(Vehicle* vehicle) {
for (auto& spot : spots) {
if (!spot) {
spot = vehicle;
return true;
}
}
return false;
}
bool removeVehicle(Vehicle* vehicle) {
for (auto& spot : spots) {
if (spot == vehicle) {
spot = nullptr;
return true;
}
}
return false;
}
};
class ParkingLot {
std::vector<Level> levels;
public:
ParkingLot(int numLevels, int numSpotsPerLevel) {
levels.resize(numLevels);
for (int i = 0; i < numLevels; i++) {
levels[i] = Level(i, numSpotsPerLevel);
}
}
bool parkVehicle(Vehicle* vehicle) {
for (auto& level : levels) {
if (level.parkVehicle(vehicle)) {
return true;
}
}
return false;
}
bool removeVehicle(Vehicle* vehicle) {
for (auto& level : levels) {
if (level.removeVehicle(vehicle)) {
return true;
}
}
return false;
}
};
public class ParkingLot {
private Level[] levels;
public ParkingLot(int numLevels, int numSpotsPerLevel) {
levels = new Level[numLevels];
for (int i = 0; i < numLevels; i++) {
levels[i] = new Level(i, numSpotsPerLevel);
}
}
public boolean parkVehicle(Vehicle vehicle) {
for (Level level : levels) {
if (level.parkVehicle(vehicle)) {
return true;
}
}
return false; // Parking failed (no spots available)
}
public boolean removeVehicle(Vehicle vehicle) {
for (Level level : levels) {
if (level.removeVehicle(vehicle)) {
return true;
}
}
return false; // Removal failed (vehicle not found)
}
}
class Vehicle:
pass
class Level:
def __init__(self, level, num_spots):
self.level_number = level
self.spots = [None] * num_spots
def park_vehicle(self, vehicle):
for i, spot in enumerate(self.spots):
if spot is None:
self.spots[i] = vehicle
return True
return False
def remove_vehicle(self, vehicle):
if vehicle in self.spots:
self.spots[self.spots.index(vehicle)] = None
return True
return False
class ParkingLot:
def __init__(self, num_levels, num_spots_per_level):
self.levels = [Level(i, num_spots_per_level) for i in range(num_levels)]
def park_vehicle(self, vehicle):
for level in self.levels:
if level.park_vehicle(vehicle):
return True
return False
def remove_vehicle(self, vehicle):
for level in self.levels:
if level.remove_vehicle(vehicle):
return True
return False
class Vehicle {}
class Level {
constructor(level, numSpots) {
this.levelNumber = level;
this.spots = new Array(numSpots).fill(null);
}
parkVehicle(vehicle) {
for (let i = 0; i < this.spots.length; i++) {
if (this.spots[i] === null) {
this.spots[i] = vehicle;
return true;
}
}
return false;
}
removeVehicle(vehicle) {
for (let i = 0; i < this.spots.length; i++) {
if (this.spots[i] === vehicle) {
this.spots[i] = null;
return true;
}
}
return false;
}
}
class ParkingLot {
constructor(numLevels, numSpotsPerLevel) {
this.levels = [];
for (let i = 0; i < numLevels; i++) {
this.levels.push(new Level(i, numSpotsPerLevel));
}
}
parkVehicle(vehicle) {
for (const level of this.levels) {
if (level.parkVehicle(vehicle)) {
return true;
}
}
return false;
}
removeVehicle(vehicle) {
for (const level of this.levels) {
if (level.removeVehicle(vehicle)) {
return true;
}
}
return false;
}
}
6. Ticket and PaymentService Classes
To manage ticketing and payments, we add the Ticket and PaymentService classes.
Ticket Class: Represents the ticket issued when a vehicle parks. It records the time the vehicle enters and exits the parking lot.
#include <ctime>
#include <chrono>
#include <cstdint>
#include <iostream>
using namespace std;
class Vehicle {}; // Placeholder for Vehicle class
class Ticket {
private:
Vehicle vehicle;
chrono::time_point<chrono::system_clock> issueTime;
chrono::time_point<chrono::system_clock> exitTime;
public:
Ticket(Vehicle vehicle) {
this->vehicle = vehicle;
this->issueTime = chrono::system_clock::now();
}
void setExitTime() {
this->exitTime = chrono::system_clock::now();
}
int64_t getDuration() {
chrono::duration<int64_t> duration = exitTime - issueTime;
return duration.count();
}
};
public class Ticket {
private Vehicle vehicle;
private Date issueTime;
private Date exitTime;
public Ticket(Vehicle vehicle) {
this.vehicle = vehicle;
this.issueTime = new Date();
}
public void setExitTime(Date exitTime) {
this.exitTime = exitTime;
}
public long getDuration() {
return (exitTime.getTime() - issueTime.getTime()) / 1000; // Time in seconds
}
}
from datetime import datetime, timedelta
class Vehicle:
pass # Placeholder for Vehicle class
class Ticket:
def __init__(self, vehicle):
self.vehicle = vehicle
self.issueTime = datetime.now()
self.exitTime = None
def setExitTime(self):
self.exitTime = datetime.now()
def getDuration(self):
if self.exitTime is None:
return 0
return (self.exitTime - self.issueTime).total_seconds()
class Vehicle {}
class Ticket {
constructor(vehicle) {
this.vehicle = vehicle;
this.issueTime = new Date();
this.exitTime = null;
}
setExitTime() {
this.exitTime = new Date();
}
getDuration() {
if (!this.exitTime) return 0;
return (this.exitTime - this.issueTime) / 1000; // Time in seconds
}
}
PaymentService Class: Responsible for calculating the parking fee and processing payments.
#include <iostream>
#include <cmath>
class Ticket {
public:
double getDuration() { return duration; }
private:
double duration = 3600.0; // Example duration
};
class PaymentService {
public:
double calculateFee(Ticket ticket) {
double duration = ticket.getDuration();
// Simple fee model: $1 per hour
return duration / 3600.0;
}
void processPayment(Ticket ticket) {
double fee = calculateFee(ticket);
std::cout << "Payment processed for $" << fee << std::endl;
}
};
public class PaymentService {
public double calculateFee(Ticket ticket) {
long duration = ticket.getDuration();
// Simple fee model: $1 per hour
return duration / 3600.0;
}
public void processPayment(Ticket ticket) {
double fee = calculateFee(ticket);
System.out.println("Payment processed for $" + fee);
}
}
class Ticket:
def get_duration(self):
return 3600.0 # Example duration
class PaymentService:
def calculate_fee(self, ticket):
duration = ticket.get_duration()
# Simple fee model: $1 per hour
return duration / 3600.0
def process_payment(self, ticket):
fee = self.calculate_fee(ticket)
print(f'Payment processed for ${fee}')
class Ticket {
getDuration() {
return 3600.0; // Example duration
}
}
class PaymentService {
calculateFee(ticket) {
let duration = ticket.getDuration();
// Simple fee model: $1 per hour
return duration / 3600.0;
}
processPayment(ticket) {
let fee = this.calculateFee(ticket);
console.log(`Payment processed for $${fee}`);
}
}
Key Design Principles in Action
1. Single Responsibility Principle (SRP): Each class has a single responsibility. The Vehicle class focuses only on vehicle details, while the ParkingSpot, Level, and ParkingLot classes handle their respective responsibilities.
2. Encapsulation: All details related to parking spots, levels, and payment processing are hidden within their respective classes.
3. Polymorphism: The canFitInSpot() method is overridden in each subclass of Vehicle, allowing different behaviors depending on the vehicle type.
4. Separation of Concerns: The system is broken down into smaller components, with the Ticket, PaymentService, and ParkingLot classes each responsible for specific parts of the process