Requirements
Determine the different ways the system will be used. This includes main functions the system needs to perform and who will use it.
=> Parking Spot Availability
-> The system should be able to tell the available spots present at each floor.
-> The spots can be divided further into categories, for example, the EVs should have separate space, the bikes have separate space etc.
=> Parking Spot Filling
-> The filling of the parking should be efficiently packed with maximum number of vehicles.
-> Few spots should be reserved for premium users and users who have pre-reserved the spot.
-> Spots of premium users should be at lower floors to reduce the timing of waiting.
=> User Roles in the System
-> Customer : It owns the Vehicle and pays the ticket fee for parking. It owns the Vehicle identity card for check in and checkouts.
-> Parking Attendant : It is responsible for checking in and checking out the Vehicle. It gots the instruction on the available parking spot.
-> Admin : It manages all the Parking Attendant and ongoing customers.
=> Fees Calculation of the Parking Spot
-> Based on the Vehicle size, the prices would vary.
-> Reservation has separate fee and that fee is dependant on the time for reservation.
-> Based on the timing of parking, the price varies.
-> Prices are based on the interval of 30min.
-> Premium users have to pay lesser amount compared to regular users.
Define Core Objects
Based on the requirements and use cases, identify the main objects of the system...
=> Vehicles
Has Attributes: Number Plate, Color, Model, Size, entryTime, exitTime
Has Method: getParkingSpot , getVehicleSize, getEntryTime, getExitTime, getUser
Constructor: new Vehicle (User Object)
Relation: Has one user mapping
=> Users
Has Attributes: Name, Id, User Role, Payment method
Has Methods: getPermissions, getUserRole, getPayment, getVehicles
Relation: Can have multiple Vehicle if user role is customer/guest user
=> Parking Spots
Has Attributes: Size, Floor, Location, Availabilty, Unqiue Id
Has Methods: getSize, getFloor, getLocation, isAvailable, getId, markUnavailable
=> Parking Lot
Has Attributes: totalSpots, availableSpots, totalFloors
Has Methods: getTotalSpots, findAvailableSpots(vehicleSize)
Analyze Relationships
Determine how these objects will interact with each other to fulfill the use cases...
Once the user is created in the system, we will add the basic details like name and phone number.
A user will book the Parking Spot in the Parking Lot: The checkAvailability function of ParkingSpot will check if the parking spot is available based on the floor and size needed.
Once the parking lot is booked for particular time, the user is prompted to do the payment for given time period, size and location.
The user then choose the payment method and do the payment. The status of payment will then be stored for the User.
The user is then allotted the parkingLot. The attendee will park the vehicle and mark it as unavailable for booking.
Establish Hierarchy
Design inheritance trees where applicable to promote code reuse and polymorphism. This step involves identifying common attributes and behaviors that can be abstracted into parent classes...
Car, MotorCycle, Truck can be inherited from Vehicle. Vehicle has these basic details : Wheels, numberPlate
Common methods could be fee calculation where we can use strategy pattern to calculate fee for different size Vehicles.
Design Patterns
Consider using design patterns (e.g., Factory, Singleton, Observer, Strategy) that fit the problem...
Factory Pattern => For checking the Fee Price for different type of vehicles.
Singleton Pattern => To maintain single instance of the ParkingLot
Observer Pattern => To notify the user about the availability of the Parking spot in the Parking Lot.
Define Class Members (write code)
Attributes: For each class, define the attributes (data) it will hold...
Methods: Define the methods (functions) that operate on the attributes. Ensure they align with the object's responsibilities and adhere to the principle of encapsulation.
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
#include <ctime>
// Enumeration for vehicle sizes.
enum VehicleSize { SMALL, MEDIUM, LARGE };
// Enumeration for payment methods.
enum PaymentMethod { CASH, CARD, ONLINE };
// Forward declaration
class User;
//---------------------//
// Vehicle Class
//---------------------//
class Vehicle {
private:
std::string numberPlate;
std::string color;
std::string model;
VehicleSize size;
time_t entryTime;
time_t exitTime;
User* owner;
public:
// Constructor associates the vehicle with a user.
Vehicle(const std::string &numPlate, const std::string &color,
const std::string &model, VehicleSize size, User* owner)
: numberPlate(numPlate), color(color), model(model), size(size), owner(owner)
{
entryTime = std::time(nullptr);
exitTime = 0;
}
// Methods to access vehicle details.
VehicleSize getVehicleSize() const { return size; }
time_t getEntryTime() const { return entryTime; }
time_t getExitTime() const { return exitTime; }
void setExitTime(time_t t) { exitTime = t; }
User* getUser() const { return owner; }
};
//---------------------//
// Observer Pattern Interfaces
//---------------------//
class Observer {
public:
virtual void update(const std::string &message) = 0;
};
class Subject {
public:
virtual void attach(Observer* observer) = 0;
virtual void detach(Observer* observer) = 0;
virtual void notify(const std::string &message) = 0;
};
//---------------------//
// User Class (implements Observer)
//---------------------//
class User : public Observer {
private:
std::string name;
int id;
std::string userRole;
PaymentMethod paymentMethod;
std::vector<Vehicle*> vehicles;
public:
User(const std::string &name, int id, const std::string &role, PaymentMethod paymentMethod)
: name(name), id(id), userRole(role), paymentMethod(paymentMethod) {}
// Register a vehicle under this user.
void addVehicle(Vehicle* vehicle) {
vehicles.push_back(vehicle);
}
const std::vector<Vehicle*>& getVehicles() const { return vehicles; }
std::string getUserRole() const { return userRole; }
PaymentMethod getPayment() const { return paymentMethod; }
// Observer update: user gets notified about parking spot availability.
void update(const std::string &message) override {
std::cout << "Notification for " << name << ": " << message << std::endl;
}
};
//---------------------//
// Parking Spot Class
//---------------------//
class ParkingSpot {
private:
VehicleSize size;
int floor;
std::string location;
bool available;
int id;
public:
ParkingSpot(VehicleSize size, int floor, const std::string &location, int id)
: size(size), floor(floor), location(location), available(true), id(id) {}
VehicleSize getSize() const { return size; }
int getFloor() const { return floor; }
std::string getLocation() const { return location; }
bool isAvailable() const { return available; }
int getId() const { return id; }
void markUnavailable() { available = false; }
void markAvailable() { available = true; }
};
//---------------------//
// Parking Lot Class (Singleton & Subject)
//---------------------//
class ParkingLot : public Subject {
private:
int totalSpots;
int availableSpots;
int totalFloors;
std::vector<ParkingSpot*> spots;
std::vector<Observer*> observers;
// Singleton instance.
static ParkingLot* instance;
// Private constructor.
ParkingLot(int totalSpots, int totalFloors)
: totalSpots(totalSpots), totalFloors(totalFloors), availableSpots(totalSpots) {}
public:
// Get the single instance (default parameters can be provided)
static ParkingLot* getInstance(int totalSpots = 100, int totalFloors = 3) {
if(!instance)
instance = new ParkingLot(totalSpots, totalFloors);
return instance;
}
// Add a parking spot to the lot.
void addParkingSpot(ParkingSpot* spot) {
spots.push_back(spot);
}
// Find available spots that can accommodate the given vehicle size.
std::vector<ParkingSpot*> findAvailableSpots(VehicleSize vehicleSize) {
std::vector<ParkingSpot*> available;
for(auto spot : spots) {
// In this simple example, a spot is considered suitable if its size is
// greater than or equal to the vehicle size (SMALL <= MEDIUM <= LARGE).
if(spot->isAvailable() && spot->getSize() >= vehicleSize) {
available.push_back(spot);
}
}
return available;
}
int getTotalSpots() const { return totalSpots; }
int getAvailableSpots() const { return availableSpots; }
// Update the availability of a parking spot and notify observers if necessary.
void updateSpotAvailability(int spotId, bool status) {
for(auto spot : spots) {
if(spot->getId() == spotId) {
if(status && !spot->isAvailable()) {
spot->markAvailable();
availableSpots++;
notify("Parking spot " + std::to_string(spotId) + " is now available.");
} else if(!status && spot->isAvailable()){
spot->markUnavailable();
availableSpots--;
}
break;
}
}
}
// Observer pattern methods.
void attach(Observer* observer) override {
observers.push_back(observer);
}
void detach(Observer* observer) override {
observers.erase(std::remove(observers.begin(), observers.end(), observer), observers.end());
}
void notify(const std::string &message) override {
for(auto observer : observers) {
observer->update(message);
}
}
};
// Initialize the singleton instance.
ParkingLot* ParkingLot::instance = nullptr;
//---------------------//
// Fee Calculator (Factory Pattern)
//---------------------//
class FeeCalculator {
public:
virtual double calculateFee(int duration) = 0; // duration in hours
virtual ~FeeCalculator() {}
};
class SmallVehicleFeeCalculator : public FeeCalculator {
public:
double calculateFee(int duration) override {
return duration * 5.0; // e.g., $5 per hour for small vehicles
}
};
class MediumVehicleFeeCalculator : public FeeCalculator {
public:
double calculateFee(int duration) override {
return duration * 7.5; // e.g., $7.5 per hour for medium vehicles
}
};
class LargeVehicleFeeCalculator : public FeeCalculator {
public:
double calculateFee(int duration) override {
return duration * 10.0; // e.g., $10 per hour for large vehicles
}
};
class FeeCalculatorFactory {
public:
static FeeCalculator* getFeeCalculator(VehicleSize size) {
switch(size) {
case SMALL: return new SmallVehicleFeeCalculator();
case MEDIUM: return new MediumVehicleFeeCalculator();
case LARGE: return new LargeVehicleFeeCalculator();
default: return nullptr;
}
}
};
//---------------------//
// Main Function (Demonstration)
//---------------------//
int main() {
// Create a singleton instance of ParkingLot.
ParkingLot* lot = ParkingLot::getInstance(10, 2);
// Create and add parking spots.
lot->addParkingSpot(new ParkingSpot(SMALL, 1, "A1", 1));
lot->addParkingSpot(new ParkingSpot(MEDIUM, 1, "A2", 2));
lot->addParkingSpot(new ParkingSpot(LARGE, 1, "A3", 3));
lot->addParkingSpot(new ParkingSpot(SMALL, 2, "B1", 4));
lot->addParkingSpot(new ParkingSpot(MEDIUM, 2, "B2", 5));
// Create a user.
User user1("John Doe", 1, "customer", CARD);
// Attach the user to receive notifications about parking availability.
lot->attach(&user1);
// Create a vehicle for the user.
Vehicle vehicle1("XYZ-123", "Red", "Sedan", SMALL, &user1);
user1.addVehicle(&vehicle1);
// User attempts to book a parking spot based on vehicle size.
std::vector<ParkingSpot*> availableSpots = lot->findAvailableSpots(vehicle1.getVehicleSize());
if(!availableSpots.empty()) {
// For demonstration, book the first available spot.
ParkingSpot* chosenSpot = availableSpots[0];
std::cout << "Booking Parking Spot ID: " << chosenSpot->getId()
<< " at location " << chosenSpot->getLocation() << std::endl;
// Mark the spot as unavailable.
lot->updateSpotAvailability(chosenSpot->getId(), false);
} else {
std::cout << "No available parking spot for your vehicle size." << std::endl;
}
// Simulate the payment process.
int parkingDuration = 2; // duration in hours
FeeCalculator* feeCalc = FeeCalculatorFactory::getFeeCalculator(vehicle1.getVehicleSize());
double fee = feeCalc->calculateFee(parkingDuration);
std::cout << "Parking Fee for " << parkingDuration << " hours: $" << fee << std::endl;
std::cout << "Processing payment using user's chosen method..." << std::endl;
// In a complete system, the payment status would be stored and managed accordingly.
// Clean up fee calculator.
delete feeCalc;
// Simulate the vehicle leaving and freeing up the spot.
lot->updateSpotAvailability(1, true);
return 0;
}
Adhere to SOLID Guidelines
Check and explain whether your design adheres to solid principles (Ask interviewer what SOLID principle is if you can not recall it.)...
Consider Scalability and Flexibility
Explain how your design can handle changes in scale and whether it would be easily to extend with new functionalities...
Create/Explain your diagram(s)
Try creating a class, flow, state and/or sequence diagram using the diagramming tool. Mermaid flow diagrams can be used to represent system use cases. You can ask the interviewer bot to create a starter diagram if unfamiliar with the tool. Briefly explain your diagrams if necessary...
Future improvements
Critically examine your design for any flaws or areas for future improvement...