SOLID Principles

Open/Closed Principle

Software entities should be open for extension but closed for modification. Add new features without changing existing code.

12 min readEssential

1What is the Open/Closed Principle?

Real-World Analogy
USB Port

Your laptop has USB ports. You can connect mouse, keyboard, camera, hard drive - anything with USB interface. The laptop is closed (you do not modify it) but open for extension (new devices work by implementing USB interface).

Plugin System

Chrome extensions add features without modifying Chrome source code. The browser is closed for modification but open for extension through the extension API.

Open/Closed Principle (OCP): Software entities (classes, modules, functions) should be open for extension (new behavior can be added) butclosed for modification (existing code should not change). This reduces the risk of breaking existing functionality when adding features.
❌ Violates OCP
Adding new shape requires modifying existing code:
if (shape == "circle") ...
else if (shape == "rectangle") ...
else if (shape == "triangle") ... // NEW
// Must modify this method!
✅ Follows OCP
Adding new shape needs only new class:
class Triangle implements Shape {
  double area() { ... }
}
// Existing code unchanged!

2OCP Violation - The Problem

Consider a discount calculator that needs if-else chains for each customer type:

OCP Violation - Discount Calculator
// ❌ BAD: Violates Open/Closed Principle
public class DiscountCalculator {

    public double calculateDiscount(String customerType, double amount) {
        double discount = 0;

        if (customerType.equals("REGULAR")) {
            discount = amount * 0.1;  // 10%
        }
        else if (customerType.equals("PREMIUM")) {
            discount = amount * 0.2;  // 20%
        }
        else if (customerType.equals("VIP")) {
            discount = amount * 0.3;  // 30%
        }
        // Adding new customer type requires modifying this method!
        // else if (customerType.equals("GOLD")) {
        //     discount = amount * 0.25;
        // }

        return discount;
    }
}

// Problems:
// 1. Every new customer type = modify existing code
// 2. Risk of breaking existing logic
// 3. Method grows indefinitely
// 4. Hard to test in isolation
// 5. Violates Single Responsibility too

3OCP Solution - Using Polymorphism

Use abstraction and polymorphism. New customer types = new classes, not modifications:

OCP Compliant - Strategy Pattern
// ✅ GOOD: Follows Open/Closed Principle

// Step 1: Define abstraction
interface DiscountStrategy {
    double calculateDiscount(double amount);
}

// Step 2: Implement for each customer type (CLOSED classes)
class RegularDiscount implements DiscountStrategy {
    @Override
    public double calculateDiscount(double amount) {
        return amount * 0.1;  // 10%
    }
}

class PremiumDiscount implements DiscountStrategy {
    @Override
    public double calculateDiscount(double amount) {
        return amount * 0.2;  // 20%
    }
}

class VIPDiscount implements DiscountStrategy {
    @Override
    public double calculateDiscount(double amount) {
        return amount * 0.3;  // 30%
    }
}

// Step 3: Calculator uses abstraction (CLOSED for modification)
class DiscountCalculator {
    private DiscountStrategy strategy;

    public DiscountCalculator(DiscountStrategy strategy) {
        this.strategy = strategy;
    }

    public double calculate(double amount) {
        return strategy.calculateDiscount(amount);
    }
}

// ========== Adding new customer type ==========
// Just add new class - no modification needed!
class GoldDiscount implements DiscountStrategy {
    @Override
    public double calculateDiscount(double amount) {
        return amount * 0.25;  // 25%
    }
}

// ========== Usage ==========
public class Main {
    public static void main(String[] args) {
        // Existing code unchanged
        DiscountCalculator regular = new DiscountCalculator(new RegularDiscount());
        System.out.println("Regular: $" + regular.calculate(100));

        DiscountCalculator vip = new DiscountCalculator(new VIPDiscount());
        System.out.println("VIP: $" + vip.calculate(100));

        // New feature - no existing code modified
        DiscountCalculator gold = new DiscountCalculator(new GoldDiscount());
        System.out.println("Gold: $" + gold.calculate(100));
    }
}
Output:
Regular: $10.0
VIP: $30.0
Gold: $25.0

4Techniques to Achieve OCP

Strategy Pattern
Encapsulate algorithms in separate classes. Switch behavior by changing strategy.
Example: DiscountStrategy for different customer types
Template Method
Define skeleton in base class, let subclasses override specific steps.
Example: ReportGenerator with customizable formatData()
Decorator Pattern
Add behavior dynamically by wrapping objects.
Example: BufferedReader wrapping FileReader
Factory Pattern
Defer object creation to subclasses or factory methods.
Example: PaymentProcessorFactory creates appropriate processor
Key Insight
OCP is achieved through abstraction. When you code to interfaces rather than implementations, you can add new implementations without touching existing code. This is why polymorphism is crucial for OCP.

5Interview Questions

What is the Open/Closed Principle?
OCP states that software entities should be open for extension (new behavior can be added) but closed for modification (existing code should not change). This means you can add new features by creating new classes/modules rather than modifying existing ones, reducing the risk of introducing bugs.
How do you achieve OCP in practice?
1) Use abstraction - depend on interfaces, not concrete classes. 2) Use polymorphism - new implementations for new behavior. 3) Use design patterns like Strategy, Decorator, Factory. 4) Apply Dependency Injection - inject dependencies rather than hardcoding. Key: code to interfaces, not implementations.
Can you give a real-world example of OCP?
JDBC in Java. The DriverManager and Connection interfaces are closed for modification. To support a new database (PostgreSQL, MySQL), you don't modify JDBC - you add a new driver implementation. Spring Framework's @Transactional works with any TransactionManager implementation. Browser extensions extend browser without modifying its source.

6Key Takeaways

1Open for extension: Add new behavior through new code.
2Closed for modification: Existing code should not change.
3Achieved through abstraction and polymorphism.
4If-else chains for types often indicate OCP violation.
5Design patterns (Strategy, Decorator, Factory) help implement OCP.
6Benefits: Less risk of bugs, easier testing, better maintainability.