CMD Guide
HomeOO & Low-Level DesignSOLID Principles

Introduction to Open Closed Principle

The Open/Closed Principle works by hiding the part that varies behind a stable abstraction — an interface or base type — so that calling code talks to the abstraction and never to the concrete variants; adding a new variant then means writing a new class the caller already knows how to use, not editing the caller. That single sentence is the whole mechanism: polymorphic dispatch through a fixed contract turns "edit existing code" into "add new code."

Bertrand Meyer's original 1988 phrasing: a module is open for extension (its behaviour can be enriched) yet closed for modification (its source — and the clients depending on it — stay frozen).

The gaming-console analogy captures it precisely: the console exposes a fixed USB / controller port (the contract). A racing wheel, a VR headset, a motion controller are new "implementations" of that port. You extend the system by plugging in, never by opening the case and re-soldering the mainboard. The port is closed; the set of things you can plug into it is open.

Why the naive design forces modification

Before any abstraction, code that varies usually lives in a growing if/else or switch on a type tag. Suppose a payments service computes a processing fee per provider:

// NAIVE — closed to nothing, open to bugs
double fee(String provider, double amount) {
    if (provider.equals("CARD"))   return amount * 0.029 + 0.30;
    if (provider.equals("UPI"))    return 0.0;
    if (provider.equals("PAYPAL")) return amount * 0.034 + 0.49;
    throw new IllegalArgumentException(provider);
}

The day Product adds CRYPTO, you must open this method and edit it. Every edit re-touches the already-tested CARD and UPI branches, risks a typo in a string compare, and re-triggers review and regression of code that did not change in intent. The method is a single point that every new provider forces you back into — the exact opposite of closed-for-modification.

The OCP shape: dispatch through a contract

Pull the varying part behind an interface and let a registry pick the implementation. Adding CRYPTO is now a brand-new file plus one registration line — the calculator engine and all three existing providers are never reopened.

interface FeePolicy {                 // the FIXED contract
    double fee(double amount);
}

class CardFee   implements FeePolicy { public double fee(double a){ return a*0.029 + 0.30; } }
class UpiFee    implements FeePolicy { public double fee(double a){ return 0.0; } }
class PaypalFee implements FeePolicy { public double fee(double a){ return a*0.034 + 0.49; } }

class FeeEngine {                     // CLOSED: never edited to add a provider
    private final Map<String, FeePolicy> registry;
    FeeEngine(Map<String, FeePolicy> registry){ this.registry = registry; }
    double charge(String provider, double amount){
        FeePolicy p = registry.get(provider);
        if (p == null) throw new IllegalArgumentException(provider);
        return p.fee(amount);          // polymorphic dispatch — engine never names CardFee etc.
    }
}

// EXTENSION — a new file, plus one registration line, zero edits to the above:
class CryptoFee implements FeePolicy { public double fee(double a){ return a*0.015; } }

Traced example — charging a $100 payment

Engine is built with registry = { CARD→CardFee, UPI→UpiFee, PAYPAL→PaypalFee }. Caller invokes engine.charge("PAYPAL", 100.00):

StepWhat happensConcrete value
1charge receives argsprovider = "PAYPAL", amount = 100.00
2registry.get("PAYPAL")returns the PaypalFee instance
3null check passesp ≠ null
4p.fee(100.00) dispatches to PaypalFee.fee100.00 × 0.034 + 0.49
5arithmetic3.40 + 0.49 = 3.89
6returnfee = $3.89

Now Product ships crypto. We add CryptoFee (above) and register CRYPTO→CryptoFee. Calling engine.charge("CRYPTO", 100.00) dispatches at step 4 to CryptoFee.fee → 100.00 × 0.015 = $1.50. Notice what we did not touch: FeeEngine, CardFee, UpiFee, PaypalFee, and every test covering them. That untouched set is what "closed for modification" buys you.

diagram
diagram

Pitfalls

When to apply it — and when not

Decision signal: reach for OCP when you can name a family of variants that will keep growing (payment providers, export formats, notification channels, shape types in a renderer) and the cost of editing a shared dispatch point is real — shared ownership, heavy test surface, deployment risk. The clincher is having already seen the second or third variant arrive.

Trade-off vs. the plain if/else / switch: the conditional is concrete, local, and trivially readable — all logic in one place — but it is the modification magnet OCP removes. OCP buys you closure at the cost of indirection (you now jump through an interface to find the real code), more classes/files, and harder "where does this actually run?" navigation. For 2–3 stable cases, the switch usually wins on clarity.

Trade-off vs. Strategy / Template Method: these are the concrete techniques that implement OCP. Strategy (composition + injected interface, as above) varies whole algorithms and is what you want when variants are independent. Template Method (an abstract base class with overridable hooks) suits the case where variants share a fixed skeleton and differ only in steps — but it leans on inheritance and bakes the call order into the base, which is harder to recombine than composed strategies.

One-line rule: choose OCP via a strategy interface when a variant family is open-ended and edits to a shared point are costly; prefer a plain switch when the cases are few and stable, and accept the edit-on-change cost as cheaper than the indirection.

Takeaways


Sources: Bertrand Meyer, Object-Oriented Software Construction (1988, 1997 2nd ed.) for the original open/closed formulation; Robert C. Martin, Agile Software Development: Principles, Patterns, and Practices (2002) and his SOLID articles for the polymorphic-abstraction reading; Gamma et al., Design Patterns (1994) for Strategy and the composition-over-inheritance trade-offs. Re-authored and deepened for this guide: replaced the definition-only treatment and decorative image with the dispatch mechanism, a traced $100 fee example, a hand-drawn naive-vs-OCP diagram, real pitfalls, and selection trade-offs versus switch / Strategy / Template Method. The runnable code is developed in the next lesson, Real World Analogies and Code Example.

Interview drills

Q1. Is OCP absolute — should you never edit existing code?
No. Bug fixes, contract changes, and refactors necessarily modify existing code. OCP only targets one kind of change: adding an anticipated new variant along a known axis without reopening the tested dispatch point. It reduces the frequency and blast-radius of edits on hot change axes; it is not a ban on all edits.

Q2. What is the classic way to fake OCP and still lose?
The "leaky registry": you move the if/else out of the engine but leave a switch that maps type tags to classes and must be edited for every new variant. You only relocated the violation. Make the new class self-register (DI container, service-loader) so adding a variant is genuinely one new file and zero edits.

Q3. OCP via Strategy vs Template Method?
Both implement OCP. Strategy composes an injected interface — independent, recombinable variants (the fee example above). Template Method uses an abstract base with overridable hooks — good when variants share a fixed skeleton and differ only in steps, but it leans on inheritance and bakes in the call order, so it is harder to recombine than composed strategies.

🤖 Don't fully get this? Learn it with Claude

Stuck on Introduction to Open Closed Principle? Open Claude, copy a block below, and it'll teach you this exact concept — visually and interactively.

🎨 Explain it visually

Build the mental picture, not memorization.

I just read a lesson on **Introduction to Open Closed Principle** (OO & Low-Level Design) and want to truly understand it. Explain Introduction to Open Closed Principle from first principles using ONE vivid real-world analogy and a visual mental model — draw it as ASCII art or a clear step-by-step diagram — with a concrete example using real numbers. Then ask me one question to check I got the mental picture, and wait for my reply. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🤔 Walk me through it (interactive)

Socratic — adapts to where you're stuck.

Teach me **Introduction to Open Closed Principle** interactively. Ask me ONE guiding question at a time, wait for my answer, and adapt to my confusion — build the idea with me step by step instead of explaining it all at once. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🧪 Quiz me & fix my gaps

Active recall exposes what you missed.

Quiz me on **Introduction to Open Closed Principle** with 5 questions, easy to tricky, ONE at a time. Tell me if each answer is right; at the end, explain clearly what I got wrong and why. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🧠 Make it stick

Intuition + hook + flashcards for long-term memory.

Help me remember **Introduction to Open Closed Principle** for the long term: give the one-sentence intuition, a memorable hook/mnemonic, a tiny worked example, and 3 active-recall flashcards (Q -> A). If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.

📝 My notes