SOLID Principle: OCP Implementation in C++ (Deep Dive)
The Open/Closed Principle (OCP) in C++ states that software entities (classes, modules, functions, etc.) should be open for extension but closed for modification. This means that you should be able to extend the behavior of a system without modifying its existing code.
For SRP (Single Responsibility Principle), check out the blog —
https://medium.com/@gmtripathi70/solid-principle-srp-implementation-in-c-deep-dive-62b8b058fcbe
Let’s make our SRP example OCP adhere,
#include<iostream>
#include<vector>
using namespace std;
class Item_abstract{
public:
virtual ~Item_abstract(){}
virtual string getId() const = 0;
virtual float getprice() const = 0;;
};
class Item : public Item_abstract{
private:
string name;
string Id;
float price;
public:
Item(string name, string Id, float price){
this->name = name;
this->Id = Id;
this->price = price;
}
string getId() const override {return Id;}
float getprice() const override {return price;}
};
class CustomerPersonalDetails_abstract{
public:
virtual ~CustomerPersonalDetails_abstract(){}
virtual void setName(string name, int Id) = 0;
virtual string getName() const = 0;
virtual int getId() const = 0;
};
class CustomerPersonalDetails : public CustomerPersonalDetails_abstract{
private:
string name;
int Id;
public:
void setName(string name, int Id) override {
this->name = name; this->Id = Id;
}
string getName()const override { return name; }
int getId() const override { return Id; }
};
class ItemDetails_Abstract{
public:
virtual ~ItemDetails_Abstract(){}
virtual void addItem(const Item& item) = 0;
virtual void removeItem(const string& itemId) = 0;
virtual float calculateTotalSum() const = 0;
virtual vector<Item> getItems() const = 0;
};
class ItemDetails : public ItemDetails_Abstract{
private:
vector<Item> items;
float totalAmount;
public:
void addItem(const Item& item) override {items.push_back(item);}
void removeItem(const string& itemId) override {
for (auto it = items.begin(); it != items.end(); ++it) {
if (it->getId() == itemId) {
items.erase(it);
break;
}
}
};
float calculateTotalSum() const override {
float total = 0;
for(auto it : items){
total += it.getprice();
}
return total;
}
vector<Item> getItems() const override {
return items;
}
};
class GenerateInvoice_Abstract{
public:
virtual ~GenerateInvoice_Abstract(){}
virtual void printInvoice(const vector<Item>& items) const = 0;
};
class GenerateInvoice : public GenerateInvoice_Abstract{
public:
void printInvoice(const vector<Item>& items) const override {
float total = 0;
for(auto it : items){
total += it.getprice();
}
cout << "Your bill is : " << total << endl;
}
};
int main() {
// Example usage
CustomerPersonalDetails customer;
customer.setName("John Doe", 123);
ItemDetails itemDetails;
itemDetails.addItem(Item("Item1", "1", 10.5));
itemDetails.addItem(Item("Item2", "2", 20.75));
GenerateInvoice invoiceGenerator;
invoiceGenerator.printInvoice(itemDetails.getItems());
return 0;
}
Output --
Your bill is : 31.25
The code above demonstrates adherence to the Open/Closed Principle (OCP). Let’s analyze how it adheres to OCP:
- Abstraction through Interfaces (Abstract Base Classes):
- The classes
Item_abstract
,CustomerPersonalDetails_abstract
,ItemDetails_Abstract
, andGenerateInvoice_Abstract
serve as abstract base classes defining interfaces for their concrete implementations. - These interfaces allow for extension by defining the contract that derived classes must adhere to without modifying the existing code.
2. Polymorphic Behavior:
- Each concrete class (
Item
,CustomerPersonalDetails
,ItemDetails
, andGenerateInvoice
) implements the methods defined in their respective abstract base classes. - This polymorphic behavior enables the client code to work with objects of these classes through their common interfaces.
3. Example Usage:
- The main function demonstrates the usage of these classes through their interfaces without needing to modify the code of the client (main) function.
- It creates instances of concrete classes (
CustomerPersonalDetails
,ItemDetails
) and uses them interchangeably through their abstract interfaces (CustomerPersonalDetails_abstract
,ItemDetails_Abstract
). - Similarly, it uses the
GenerateInvoice_Abstract
interface to invoke theprintInvoice
method of theGenerateInvoice
class.
4. Extensibility:
- You can easily extend the functionality of the system by creating new classes that implement the existing abstract interfaces.
- For example, you can create a new class that implements
ItemDetails_Abstract
to handle items in a different way, and the client code won't need to change.
In conclusion, the code above effectively adheres to the Open/Closed Principle by allowing for extension without modification, thanks to the use of abstract interfaces and polymorphism.