Do you think IIT Guwahati certified course can help you in your career?
No
Introduction
This article explains how to design a parking lot system usingObject-Oriented Programming (OOP) principles and SOLID principles in C++. It defines classes for different types of vehicles, such as cars and bikes, and classes for different types of payment structures, such as hourly and handicapped rates.
The system is designed to be scalable, efficient, secure, reliable, and user-friendly. Additionally, it has been created keeping in mind maintainability, reusability, and testability.
This article will take you through the various components and the process of designing a parking lot system using OOPs and SOLID principles in C++.
System Requirements
We will gather functional and non-functional requirements in this project.
What are Functional Requirements?
Functional requirements are specific actions or tasks that a system or product must be able to perform. They describe what the system should do and how it should behave, defining the functionalities and features that the system must have to meet the end-users needs.
Functional Requirements for Parking Lot
The following is a list of functional requirements for a parking lot system:
The parking lot system should be able to park cars, bikes, and handicapped vehicles.
The system should be able to calculate the cost of parking for each type of vehicle.
The system should be able to keep track of the time a vehicle is parked.
The system should be able to remove a vehicle from a parking spot.
The system should be able to check the availability of parking spots and handicapped spots on a specific floor.
The system should be able to calculate the number of hours a vehicle has been parked.
What are Non-Functional Requirements?
Non-functional requirements are constraints, or quality attributes that a system must meet, such as performance, security, usability, and maintainability. They describe how well the system should perform, not what it should do. Non-functional requirements specify the system's desired performance, security, usability, and other characteristics.
Non-Functional Requirements for Parking Lot
The following is a list of functional requirements for a parking lot system:
The parking lot system should handle many vehicles and parking spots.
The system should be efficient and respond quickly to user requests.
The system should be secure and prevent unauthorized access to the parking spots.
The system should be user-friendly and easy to understand for the users.
The system should have a well-designed interface to make it easy to use.
The system should be easily extensible and maintainable for future updates and changes.
The system should be tested and debugged thoroughly to ensure it is stable and reliable.
Class Diagram
A class diagram in UML (Unified Modeling Language) is a visual representation of the structure and relationships of classes in a software system.
The class diagram serves as a blueprint for the program, outlining the components and their interactions.
Gain a Complete Understanding of Class Diagrams: Definition and Creation. Check out this article for more information.
Use Case Diagram
A Use Case Diagram is a visual representation of the interactions between the actors and a system. By using a Use Case Diagram, it is possible to see the functionalities offered by the system and how the actors interact with it. This can help to identify the requirements for the system and improve its design.
Implementation in C++
#include <iostream>
#include <vector>
#include <chrono>
class Payment {
public:
virtual double calculateCost(double hours) = 0;
};
class CarPayment : public Payment {
public:
double calculateCost(double hours) { return hours * 2; }
};
class BikePayment : public Payment {
public:
double calculateCost(double hours) { return hours * 1; }
};
class HandicappedPayment : public Payment {
public:
double calculateCost(double hours) { return 0; }
};
class Vehicle {
protected:
Payment* payment;
std::chrono::time_point<std::chrono::system_clock> parkedTime;
public:
virtual std::string getType() = 0;
virtual double calculateCost(double hours) {
return payment->calculateCost(hours);
}
void setParkedTime() {
parkedTime = std::chrono::system_clock::now();
}
std::chrono::time_point<std::chrono::system_clock> getParkedTime() {
return parkedTime;
}
};
class Car : public Vehicle {
public:
Car() { payment = new CarPayment(); }
std::string getType() { return "Car"; }
};
class Bike : public Vehicle {
public:
Bike() { payment = new BikePayment(); }
std::string getType() { return "Bike"; }
};
class HandicappedVehicle : public Vehicle {
public:
HandicappedVehicle() { payment = new HandicappedPayment(); }
std::string getType() { return "Handicapped"; }
};
class ParkingLot {
private:
std::vector<std::vector<std::vector<Vehicle*>>> spots;
int floors;
int rows;
int spotsPerRow;
public:
ParkingLot(int floors, int rows, int spotsPerRow) {
this->floors = floors;
this->rows = rows;
this->spotsPerRow = spotsPerRow;
spots.resize(floors);
for (int i = 0; i < floors; i++) {
spots[i].resize(rows);
for (int j = 0; j < rows; j++) {
spots[i][j].resize(spotsPerRow);
}
}
}
bool park(Vehicle* v, int floor, int row, int spot) {
if (spots[floor][row][spot] == nullptr) {
spots[floor][row][spot] = v;
std::cout << v->getType() << " parked successfully at floor " << floor << ", row " << row << ", spot " << spot << "." << std::endl;
return true;
} else {
std::cout << "Spot already occupied." << std::endl;
return false;
}
}
bool leave(Vehicle* v) {
for (int i = 0; i < floors; i++) {
for (int j = 0; j < rows; j++) {
for (int k = 0; k < spotsPerRow; k++) {
if (spots[i][j][k] == v){
double hours = calculateHoursParked(spots[i][j][k]);
double cost = spots[i][j][k]->calculateCost(hours);
spots[i][j][k] = nullptr;
std::cout << v->getType() << " left successfully. Total cost: " << cost << std::endl;
return true;
}
}
}
}
std::cout << v->getType() << " not found." << std::endl;
return false;
}
int availableSpots(int floor) {
int count = 0;
for (int i = 0; i < rows; i++) {
for (int j = 0; j < spotsPerRow; j++) {
if (spots[floor][i][j] == nullptr) {
count++;
}
}
}
return count;
}
int handicappedSpots(int floor) {
int count = 0;
for (int i = 0; i < rows; i++) {
for (int j = 0; j < spotsPerRow; j++) {
if (dynamic_cast<HandicappedVehicle*>(spots[floor][i][j]) != nullptr) {
count++;
}
}
}
return count;
}
double calculateHoursParked(Vehicle* v) {
for (int i = 0; i < floors; i++) {
for (int j = 0; j < rows; j++) {
for (int k = 0; k < spotsPerRow; k++) {
if (spots[i][j][k] == v) {
// get the current time
auto now = std::chrono::system_clock::now();
// get the time the vehicle was parked
auto parkedTime = spots[i][j][k]->getParkedTime();
// calculate the difference in hours
auto duration = std::chrono::duration_cast<std::chrono::hours>(now - parkedTime);
return duration.count();
}
}
}
}
return 0;
}
};
int main() {
ParkingLot lot(3, 10, 20);
Car car1, car2;
Bike bike1, bike2;
HandicappedVehicle hv1;
car1.setParkedTime();
lot.park(&car1, 0, 0, 0);
car2.setParkedTime();
lot.park(&car2, 0, 0, 1);
bike1.setParkedTime();
lot.park(&bike1, 0, 0, 2);
hv1.setParkedTime();
lot.park(&hv1, 2, 9, 19);
std::cout << "Available spots on floor 0: " << lot.availableSpots(0) << std::endl;
std::cout << "Handicapped spots on floor 2: " << lot.handicappedSpots(2) << std::endl;
lot.leave(&car1);
lot.leave(&bike2);
std::cout << "Available spots on floor 0: " << lot.availableSpots(0) << std::endl;
return 0;
}
You can also try this code with Online C++ Compiler
Car parked successfully at floor 0, row 0, spot 0.
Car parked successfully at floor 0, row 0, spot 1.
Bike parked successfully at floor 0, row 0, spot 2.
Handicapped parked successfully at floor 2, row 9, spot 19.
Available spots on floor 0: 197
Handicapped spots on floor 2: 1
Car left successfully. Total cost: 0
Bike not found.
Available spots on floor 0: 198
Code Explanation
Here is a summary of where the different object-oriented principles and SOLID principles are used in the provided code:
Object-Oriented Principles
Inheritance: The Car, Bike, and HandicappedVehicle classes inherit from the Vehicle class.
Polymorphism: The Vehicle class has a virtual function calculateCost(double hours) which is overridden in the derived classes CarPayment, BikePayment, and HandicappedPayment.
Encapsulation: The member variables and functions in the classes are all marked as either private or protected so that they can only be accessed by the class itself or derived classes.
SOLID Principles
Single Responsibility Principle (SRP): Each class has a single responsibility; for example, the Payment class is responsible for calculating the cost of parking, the Vehicle class is responsible for storing information about the parked vehicle, and the ParkingLot class is responsible for managing the parking spots.
Open/Closed Principle (OCP): The Payment class is open for extension but closed for modification, meaning that new payment types can be added by creating new classes that inherit from Payment, but the Payment class itself does not need to be modified.
Liskov Substitution Principle (LSP): The derived classes CarPayment, BikePayment, and HandicappedPayment can be used interchangeably with the base class Payment without affecting the correctness of the program.
Interface Segregation Principle (ISP): The Vehicle class only requires the methods it needs from the Payment class and does not impose unnecessary methods.
Dependency Inversion Principle (DIP): The Vehicle class depends on the interface of the Payment class rather than the implementation, allowing for flexibility in the payment system.
It's worth noting that this is a simplified example, and in a real-world scenario, it might be more complex to implement all of these principles.
For a Thorough Understanding of Object-Oriented Programming in C++, visit this article, and for in-depth knowledge of essential software principles, see this resource.
Data Structures and Algorithms Used
In the provided code, the main data structure used is a 3-dimensional vector of type std::vector<std::vector<std::vector<Vehicle*>>>. This is used to store the parking spots in the parking lot and allows for easy access and manipulation of the spots. The first dimension represents the floor, the second represents the row, and the third represents the spot. Using this data structure, it's easy to check the availability of a spot by checking if it's empty or occupied and also to remove a vehicle from a specific spot by setting the spot to null.
In terms of algorithms, the main algorithm used is the one in the calculateHoursParked(Vehicle* v) function, which calculates the number of hours a vehicle has been parked. This function uses the std::chrono::duration_cast<std::chrono::hours> function to calculate the duration between the parked time of the vehicle and the current time and convert it to hours. This algorithm allows for accurate calculation of the duration of parking and makes it easy to calculate the cost of parking.
The use of a 3-dimensional vector is suitable for this problem as it allows for easy organization and manipulation of the parking spots. Also, it's very efficient as it has a constant time complexity for accessing elements.
Using std::chrono is also suitable as it provides a simple and efficient way to get the current time and calculate durations.
Overall, these data structures and algorithms were used because they provide an efficient and effective way to implement the functionality of the parking lot system, allowing for easy manipulation and organization of the parking spots and accurate calculation of the duration of parking.
Frequently Asked Questions
What is a Load Balancer?
A load balancer is a system design component that distributes incoming network traffic across multiple servers to optimize resource utilization, improve responsiveness, and maximize system availability.
What is fault tolerance?
Fault tolerance in system design is the ability of a system to continue functioning normally, even in the presence of hardware or software failures.
What is meant by Redundancy?
Redundancy in system design is the duplication of critical components or resources within a system to ensure the system's continued availability in the event of a failure. It involves creating multiple copies of the same data, software, hardware, or network components to provide a backup resource in the case of an unexpected outage.
Conclusion
In conclusion, the provided code implements a parking lot system using C++ and adhering to the SOLID principles. Using object-oriented programming concepts such as inheritance and polymorphism allowed for the system's clear separation of concerns and easy extensibility.
The use of a 3-dimensional vector data structure and the algorithm used to calculate the duration of parking are well suited for the problem at hand and provide an efficient way to store and manipulate parking spots and calculate the cost of parking.
The implementation is well organized and easy to understand, making it easy to maintain and update in the future. Overall, this parking lot system is robust, efficient, and well-designed, which can serve as a good reference for similar projects.
Now that you have a solid understanding of the low-level design of a Parking lot, you can tackle various system design-related problems posed by product-based companies. You can also consider our System Design Course to give your career an edge over others.
We hope this blog post was helpful to you. Please share your feedback in the comments section.