O · SOLID
Open / Closed Principle
Software should be open for extension but closed for modification. Add new behavior by adding new code, not by editing code that already works.
✗ The problem
The growing
The growing if / else
Every new shipping method forces an edit to calculateCost.
Click a new requirement — watch it reopen a function that already worked.
function calculateCost(order) {
if (order.method === 'standard') return order.weight * 2;
if (order.method === 'express') return order.weight * 5;
if (order.method === 'drone') return order.weight * 9 + 20;
if (order.method === 'intl') return order.weight * 12 + customs(order);
}
calculateCost()
knows every method
Each edit re-touches a tested function. More
ifs = more risk,
harder tests, merge conflicts. The function is never "done".
✓ The refactor
↑
Depend on an abstraction
Define a common shape. Each method becomes its own small, self-contained strategy.
interface ShippingMethod {
cost(order): number
}
class Standard implements ShippingMethod {
cost(o) { return o.weight * 2 }
}
class Express implements ShippingMethod {
cost(o) { return o.weight * 5 }
}
ShippingMethod
interface · cost()
Standard
Express
✓ Extend by adding
↑
New method? New class. Nothing else changes.
The calculator just calls method.cost(order). Add a class, register it — done.
// unchanged, forever:
function calculateCost(order, method) {
return method.cost(order);
}
// new requirement = a brand new file,
// zero edits to the code above
class Drone implements ShippingMethod {
cost(o) { return o.weight * 9 + 20 }
}
ShippingMethod
Standard
Express
Drone
added later ✨
✓ Takeaway
Closed to edits, open to growth
- Smell: a
switch/if-elsechain you re-edit for every new case. - Fix: program to an interface; put each variant behind it.
- Payoff: tested code stays untouched; new features can't regress old ones.
- Careful: add the abstraction when the 2nd or 3rd case appears — not preemptively (YAGNI).
Related: this is the pattern mindset → — small
interchangeable pieces behind a stable contract.