Definition
The Open-Closed Principle (OCP) is the second of the five SOLID principles of object-oriented design. It 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 add new functionality to a system without changing existing code. The goal is to make the system easy to extend with new behaviors without having to modify the classes that already exist.
class AreaCalculator {
calculateArea(shapes) {
let area = 0;
for (let shape of shapes) {
if (shape.type === 'circle') {
area += Math.PI * shape.radius * shape.radius;
}
else if (shape.type === 'rectangle') {
area += shape.width * shape.height;
}
// What if we want to add a triangle? We'd have to modify this class!
}
return area;
}
}
This implementation violates the Open-Closed Principle for several reasons:
- To add a new shape type (like a triangle), we must modify the
AreaCalculator
class. - The
calculateArea
method contains conditional logic that checks the type of shape. - Each new shape requires a new conditional branch, making the code increasingly complex.
- Changes to existing shape calculations risk breaking the entire calculator.
- The class is not closed for modification, as it needs to be changed whenever a new shape is added.
// Base Shape class or interface
class Shape {
calculateArea() {
// This should be implemented by subclasses
throw new Error("Method 'calculateArea()' must be implemented.");
}
getDescription() {
// This should be implemented by subclasses
throw new Error("Method 'getDescription()' must be implemented.");
}
}
// Rectangle implementation
class Rectangle extends Shape {
constructor(width, height) {
super();
this.width = width;
this.height = height;
}
calculateArea() {
return this.width * this.height;
}
getDescription() {
return `Rectangle: ${this.width} × ${this.height}`;
}
}
// Circle implementation
class Circle extends Shape {
constructor(radius) {
super();
this.radius = radius;
}
calculateArea() {
return Math.PI * this.radius * this.radius;
}
getDescription() {
return `Circle: radius ${this.radius}`;
}
}
// We can add a new shape without modifying existing code
class Triangle extends Shape {
constructor(base, height) {
super();
this.base = base;
this.height = height;
}
calculateArea() {
return 0.5 * this.base * this.height;
}
getDescription() {
return `Triangle: base ${this.base}, height ${this.height}`;
}
}
// Area calculator that works with any Shape
class AreaCalculator {
calculateTotalArea(shapes) {
return shapes.reduce((sum, shape) => sum + shape.calculateArea(), 0);
}
}
This implementation follows the Open-Closed Principle:
- The
AreaCalculator
class is closed for modification - it doesn't need to change when new shapes are added. - The system is open for extension - we can add new shapes by creating new subclasses of
Shape
. - Each shape is responsible for calculating its own area through the
calculateArea()
method. - Adding a new shape (like
Triangle
) doesn't require changing any existing code. - The code is more maintainable and less prone to bugs when extending functionality.
Interactive Demo: Area Calculator
Click the buttons below to add different shapes. The area calculator will compute the total area without needing to be modified for each shape type.