Web Development

Real-World Examples of Violating the Open-Closed Principle

Let's explore some common examples where the Open-Closed Principle is violated and explore how these issues can be addressed through refactoring.

Mar 27, 2024 3 min read
Real-World Examples of Violating the Open-Closed Principle

Object-oriented programming has a few core principles worth internalizing, and the Open-Closed Principle (OCP) is one of the most practical. It says software entities should be open for extension but closed for modification. In practice, developers run into OCP violations often — the result is code that's brittle, bug-prone, and painful to extend. Let's look at some concrete examples and how refactoring can fix them.

Example 1: Conditional Statements Proliferation

Issue: One common violation of the Open-Closed Principle occurs when conditional statements proliferate throughout the codebase. For instance, consider a scenario where a system needs to calculate the shipping cost based on various factors such as destination, weight, and shipping method. Without adhering to the OCP, developers might end up with a series of if-else statements scattered across the codebase to handle different shipping scenarios.

if shipping_method == 'standard':
    shipping_cost = calculate_standard_shipping_cost(destination, weight)
elif shipping_method == 'express':
    shipping_cost = calculate_express_shipping_cost(destination, weight)
elif shipping_method == 'priority':
    shipping_cost = calculate_priority_shipping_cost(destination, weight)
# More conditions added for different scenarios

Solution: To adhere to the Open-Closed Principle, we can use polymorphism and abstraction to encapsulate the varying behavior of shipping methods. By defining an abstract ShippingMethod interface or base class and implementing concrete subclasses for each shipping method, we get a more flexible and extensible design.

class ShippingMethod:
    def calculate_shipping_cost(self, destination, weight):
        pass

class StandardShipping(ShippingMethod):
    def calculate_shipping_cost(self, destination, weight):
        # Calculate standard shipping cost
        pass

class ExpressShipping(ShippingMethod):
    def calculate_shipping_cost(self, destination, weight):
        # Calculate express shipping cost
        pass

class PriorityShipping(ShippingMethod):
    def calculate_shipping_cost(self, destination, weight):
        # Calculate priority shipping cost
        pass

By following this approach, adding new shipping methods in the future becomes as simple as creating a new subclass of ShippingMethod, thus adhering to the Open-Closed Principle.

Example 2: Hard-Coded Dependencies

Issue: Another common violation of the Open-Closed Principle occurs when classes have hard-coded dependencies on concrete implementations rather than abstractions. This tightly couples classes together, making it challenging to extend or modify the system without modifying existing code.

public class OrderProcessor {
    private DatabaseConnector dbConnector;

    public OrderProcessor() {
        this.dbConnector = new DatabaseConnector(); // Concrete dependency
    }

    public void processOrder(Order order) {
        // Process order logic
        dbConnector.saveOrder(order); // Hard-coded dependency
    }
}

Solution: To adhere to the Open-Closed Principle, we can introduce dependency inversion by programming to interfaces instead of concrete implementations. By injecting dependencies via constructor or setter methods, we can decouple classes and make them more open for extension.

public class OrderProcessor {
    private DatabaseConnector dbConnector;

    public OrderProcessor(DatabaseConnector dbConnector) {
        this.dbConnector = dbConnector; // Dependency injected via constructor
    }

    public void processOrder(Order order) {
        // Process order logic
        dbConnector.saveOrder(order); // Dependency is now abstract
    }
}

By following this approach, we can easily replace the DatabaseConnector with a different implementation without modifying the OrderProcessor class, thus adhering to the Open-Closed Principle.

Conclusion

OCP violations make codebases fragile and rigid — the kind where every new requirement means touching existing logic and risking regressions. Abstraction, polymorphism, and dependency inversion give you a way out. Once you've refactored a few real examples this way, the pattern becomes second nature. The payoff isn't just cleaner design; it's a codebase that new contributors can extend without needing to understand every existing branch.

Open-Closed ViolationsProgramming TipsClean Code
Grow your business with us

Take your business to the next level.

Tell us what you're building. We'll come back inside one business day with a fixed scope, timeline, and team — or an honest “this isn't a fit”.

ENGINEERING PHILOSOPHY

Code is useless if it's not comprehensible to those who maintain it. We write code the next person can actually understand.