Open-Closed Principle: Extend Without Modifying
What This Concept Is
The Open-Closed Principle (OCP), due to Bertrand Meyer and popularized by Robert Martin, says:
Software entities should be open for extension, but closed for modification.
"Open for extension" means new behavior can be added. "Closed for modification" means existing, tested code does not need to be edited to add that behavior. The usual mechanism is an abstraction: the stable code depends on an interface, and new behavior arrives as a new implementation of that interface.
OCP is about change locality: when a new variant arrives, it should show up in one new place, not as a shotgun blast of edits across the codebase.
Why It Matters Here
OCP is how a design absorbs change without destabilizing what already works. Code that violates OCP tends to accumulate if branches, switch cases, or enum explosions, each one a new regression risk.
It also connects directly to later modules: strategy, decorator, and template method patterns are all OCP moves. Without the principle, the patterns feel arbitrary.
Concrete Example
OCP violation -- every new shape edits AreaCalculator:
class AreaCalculator:
def area(self, shape):
if shape.kind == "circle":
return 3.14 * shape.radius ** 2
elif shape.kind == "rectangle":
return shape.width * shape.height
# adding Triangle means editing this function
Every time a new shape type is introduced, this file is reopened and modified. All other callers relying on AreaCalculator must be re-tested.
OCP-respecting version -- new shapes arrive as new classes:
class Shape:
def area(self): raise NotImplementedError
class Circle(Shape):
def __init__(self, r): self.r = r
def area(self): return 3.14 * self.r ** 2
class Rectangle(Shape):
def __init__(self, w, h): self.w, self.h = w, h
def area(self): return self.w * self.h
def total_area(shapes): return sum(s.area() for s in shapes)
total_area is now closed for modification. Triangle arrives as a new class, not as an edit.
Common Confusion / Misconception
"OCP means never edit old code." No. You edit old code for bug fixes, performance, and new responsibilities -- that is normal work. OCP constrains a narrower case: adding a new variant of the same kind of thing should not force you to edit the code that ranges over the variants.
A more dangerous misconception: "I should always make things extensible, just in case." That is speculative generality (see Cluster 5). Apply OCP when the axis of variation is real -- you have at least two variants today, or a credible third is about to arrive. Inventing abstractions for hypothetical future variants is how designs get worse.
How To Use It
OCP is a response to change pressure, not a preemptive design strategy:
- Notice when a new variant of the same kind is arriving (new payment method, new report, new export format).
- Check whether the code that ranges over existing variants has a central switch, if-chain, or enum match.
- Introduce an interface whose name captures what varies (
PaymentMethod,Report,Exporter). - Move each existing variant's behavior behind the interface.
- Add new variants by writing new implementations, not by editing the dispatcher.
Check Yourself
- Why is OCP a response to change, not a preemptive design?
- What pattern from Cluster 4's smell list (primitive obsession, repeated switches) usually signals a missed OCP?
- What is the difference between "closed for modification" and "immutable"?
Mini Drill or Application
Find a switch or if-chain in code you know that branches on a type, kind, or category field. Do all four:
- Name the axis of variation (what changes each case?).
- Sketch an interface whose methods cover the operations done per case.
- Show one existing case as an implementation of that interface.
- Describe the hypothetical "new variant" change under the old design vs the new design.