Back to SOLID Principles

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.

Bad Example: Violating OCP
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;
    }
}
Why This Violates OCP:

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.
Good Example: Following OCP
// 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);
    }
}
Why This Follows OCP:

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.

Shapes:

No shapes added yet.

Total Area:

Total area: 0
Back to Main Page