Open/Closed Principle (OCP)
The Open/Closed Principle is the “O” in SOLID. It was defined by Bertrand Meyer and popularized by Robert C. Martin. It states:
“Software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification.”
The Core Idea
You should be able to add new functionality to a system without changing the existing source code. Instead of editing an existing class, you should extend it (via inheritance or composition).
The Problem with Modification
When you modify existing code:
- You risk breaking existing features (regression).
- You must re-test all dependent modules.
- The code becomes a “God Object” filled with
if-elseorswitchstatements.
Example: Area Calculator
The Bad Way (Violating OCP)
class Rectangle {
public double width;
public double height;
}
class AreaCalculator {
public double calculateArea(Object[] shapes) {
double area = 0;
for (Object shape : shapes) {
if (shape instanceof Rectangle) {
Rectangle r = (Rectangle) shape;
area += r.width * r.height;
}
// If we add 'Circle', we MUST modify this class!
}
return area;
}
}
The Good Way (Applying OCP)
We use abstraction to make the calculator “closed” to changes in shape types.
interface Shape {
double getArea();
}
class Rectangle implements Shape {
public double width, height;
public double getArea() { return width * height; }
}
class Circle implements Shape {
public double radius;
public double getArea() { return Math.PI * radius * radius; }
}
class AreaCalculator {
public double calculateArea(Shape[] shapes) {
double area = 0;
for (Shape shape : shapes) {
area += shape.getArea();
}
return area;
}
}
Now, if we want to add a Triangle, we just create a new class. We never touch AreaCalculator again.
OCP in C++: The Strategy Pattern
C++ often uses pointers to abstract classes to implement OCP.
class Logger {
public:
virtual void log(string msg) = 0;
};
class ConsoleLogger : public Logger {
void log(string msg) override { cout << msg << endl; }
};
class FileLogger : public Logger {
void log(string msg) override { /* write to file */ }
};
class App {
Logger* logger;
public:
App(Logger* l) : logger(l) {}
void doSomething() {
logger->log("Something happened");
}
};
When to Apply OCP
OCP is powerful but can lead to over-engineering. Apply it:
- At architectural boundaries.
- When you anticipate that a logic point will have multiple variations in the future.
- When using plugin architectures.
Summary
OCP is about looking into the future. By using interfaces and abstractions, we create systems that are “immune” to changes in business requirements. We grow our software by adding new code, not by hacking old code.