OCP Trade-Offs: When to Use and When to Avoid
When OCP Is Appropriate
1. Anticipated Variation: When you know a specific dimension will vary (payment methods, notification channels, pricing rules), OCP prevents cascading changes. For example, an e-commerce system will likely add new payment providers over time.
2. Plugin Architectures: Systems designed for third-party extensions must use OCP. A library management system allowing custom borrowing policies or a vending machine supporting custom product dispensing logic both benefit from OCP.
3. Existing Stable Code: When modifying production code carries high risk (financial systems, medical devices), extending through new classes is safer than editing tested code. The "closed" part of OCP provides a stability guarantee.
When OCP Is Overkill
1. Truly Stable Requirements: If a domain has genuinely fixed behavior, abstraction adds unnecessary complexity. For example, if a parking lot will only ever support cars and motorcycles (contractually limited), creating a Vehicle abstraction for future vehicle types is premature optimization.
2. Trivial or One-Time Logic: Simple utility functions or calculations that rarely change do not benefit from OCP. A method that converts temperature units does not need a TemperatureConverter interface with CelsiusToFahrenheit and FahrenheitToCelsius implementations.
3. Rapid Prototyping Phase: During initial exploration, requirements are unclear. Premature abstraction can lock you into the wrong design. It is better to violate OCP initially, then refactor toward it once patterns emerge. But do not use prototyping as an excuse to avoid design forever.
Comparison with Alternatives
| Approach | When to Use | Trade-Off |
|---|---|---|
| OCP (Abstraction) | Expected variation in specific dimension | More classes, indirection; harder to trace execution |
| Configuration/Data | Variation in parameters, not behavior | Cannot express complex logic; requires external configuration management |
| Direct Modification | Stable code, low risk, infrequent changes | Every change requires testing entire module; higher risk of regression |
Common Pitfalls
Over-Abstraction: Creating interfaces for every class "just in case" leads to bloated designs. Abstract only the parts that truly vary. If you have one implementation and no concrete plan for a second, you probably do not need the abstraction yet.
Wrong Abstraction: Identifying the stable vs. varying parts incorrectly leads to abstractions that change frequently, violating the "closed" part of OCP. For example, if you abstract Vehicle but the parking fee calculation algorithm itself changes often, you have abstracted the wrong dimension.
Ignoring Existing Extension Mechanisms: Some languages and frameworks provide built-in extension points (callbacks, hooks, events). Using these is simpler than designing custom abstractions. However, if these mechanisms are not type-safe or lack clear contracts, OCP-style interfaces may still be preferable.