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.

You add a new shipping method. You open the file. You find six existing if-else branches, each waiting for a seventh. That moment is what an Open-Closed Principle (OCP) violation feels like. The rule is simple: software entities should be open for extension but closed for modification. When code breaks it, the result is brittle logic that grows more dangerous with every new requirement. Here are two concrete examples and the refactors that fix them.
Example 1: Conditional Statements Proliferation
Issue. Take a system that calculates shipping cost based on destination, weight, and method. Without OCP, you get if-else chains scattered across the codebase. Every new shipping method means touching existing logic. The chain grows; so does the risk.
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. Define an abstract ShippingMethod base class and give each method its own subclass. Adding standard, express, or priority shipping means writing a new subclass, not touching existing ones. Polymorphism handles the dispatch; the original code stays unchanged.
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
A new carrier? New subclass. Nothing else changes.
Example 2: Hard-Coded Dependencies
Issue. When a class instantiates its own dependencies directly, you've wired the implementation into the constructor. Swap out DatabaseConnector for a test double or a different backend? You're editing OrderProcessor to do it. That's the violation: the class isn't closed to modification.
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. Inject the dependency instead of constructing it. Program to an interface, pass the concrete implementation from outside, and OrderProcessor no longer needs to know which storage backend is in use. The class stays closed; the caller decides what gets injected.
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
}
}
Need a different database? Pass a different connector. OrderProcessor never changes.
Conclusion
OCP violations share a common shape: every new requirement forces you back into logic that already works, and each touch risks a regression. Abstraction, polymorphism, and dependency inversion break that pattern. Work through a few real refactors and the instinct kicks in early, before the if-else tree takes root. New contributors can extend the system without mapping every existing branch first. That's the real payoff.
Working on something like this?
Get a fixed scope, timeline, and price within one business day — no obligation.


