Skip to main content

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:

import math

class AreaCalculator:
def area(self, shape):
if shape.kind == "circle":
return math.pi * shape.radius ** 2
elif shape.kind == "rectangle":
return shape.width * shape.height
# adding Triangle means editing this function

Adding a shape edits this dispatcher. Assess whether that is costly under the actual extension pattern; a small, closed variant set can be clearer as a central match. Relevant regression tests are needed in either design.

OCP-respecting version -- new shapes arrive as new classes:

import math

class Shape:
def area(self): raise NotImplementedError

class Circle(Shape):
def __init__(self, r): self.r = r
def area(self): return math.pi * 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 can accept a new shape implementing area without an edit. Construction/registration code and tests may still change. Adding a new operation such as export may instead touch every shape: closure is relative to an extension axis. These sketches assume finite nonnegative dimensions and omit input validation.

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 a specific extension boundary has a present benefit or credible change pressure; variant count alone does not establish that benefit. 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:

  1. Notice when a new variant of the same kind is arriving (new payment method, new report, new export format).
  2. Check whether the code that ranges over existing variants has a central switch, if-chain, or enum match.
  3. Introduce an interface whose name captures what varies (PaymentMethod, Report, Exporter).
  4. Move each existing variant's behavior behind the interface.
  5. Add new variants by writing new implementations, not by editing the dispatcher.

Check Yourself​

  1. Why is OCP a response to change, not a preemptive design?
  2. What pattern from Cluster 4's smell list (primitive obsession, repeated switches) usually signals a missed OCP?
  3. 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:

  1. Name the axis of variation (what changes each case?).
  2. Sketch an interface whose methods cover the operations done per case.
  3. Show one existing case as an implementation of that interface.
  4. Describe the hypothetical "new variant" change under the old design vs the new design.

Read This Only If Stuck​

Engineering extension: An extension axis has a dual cost​

A hierarchy makes adding a new shape convenient when the operation set is stable. A closed tagged representation can make adding a new operation convenient when the variants are stable. Neither closes every future change. A small dispatch table of pure functions is a third option. Try: assess adding Triangle versus adding serialize. Check: include registration, compatibility, validation and testing in the edit surface, not only the main loop.

Use the design contracts and practice feedback after an independent attempt.