Stable core, variable edges
Keep the main workflow stable while pushing foreseeable variation into policies or implementations.
OCP says software entities should be open for extension but closed for modification. That does not mean code should never change. It means the stable core should not need repeated rewrites for every predictable variation. In OOAD, OCP is about choosing where variation belongs.
Order-management systems regularly gain new discount types, payment methods, shipping carriers, and notification channels. If every new option requires rewriting the same service class, variation is not localized. OCP helps create extension points around that expected change.
OCP is strongest when the business flow is stable but one part of the behavior varies. That usually points toward strategies, policies, plugin-style collaborators, or focused interfaces.
In Java, OCP often appears through dependency injection and polymorphism, but the real design question is earlier: what should vary independently from the core use case? If that question is answered well, the code structure follows naturally.
Bad OCP attempts produce speculative abstractions. Good OCP identifies actual, recurring variation and gives it a clean seam.
Keep the main workflow stable while pushing foreseeable variation into policies or implementations.
An abstraction only helps if the variation is real and recurring.
You cannot make the system open for the right extensions if the domain boundaries are unclear.
package org.javaomnibus.ecommerce.principles;
import java.util.List;
public final class DiscountEngine {
private final List discountRules;
public DiscountEngine(List discountRules) {
this.discountRules = List.copyOf(discountRules);
}
public Money apply(Order order, Money subtotal) {
Money current = subtotal;
for (DiscountRule discountRule : discountRules) {
current = discountRule.apply(order, current);
}
return current;
}
}
interface DiscountRule {
Money apply(Order order, Money subtotal);
}
record Order(String orderId) {}
record Money(java.math.BigDecimal value, String currency) {}
OCP can create unnecessary abstractions if the variation never materializes or stays trivial. Not every `if` statement is a design smell. The cost of extension should be lower than the cost of indirection you introduce.