CMD Guide
HomeOO & Low-Level DesignSOLID Principles

Final Thoughts on the OpenClosed Principle and Its Relation to Other SOLID Principles

The Open/Closed Principle works because a stable abstraction (an interface or abstract base) lets the caller dispatch to behavior chosen at runtime by the object's concrete type, so a new behavior is added by writing a new class the caller already knows how to invoke — never by re-opening and re-editing the caller. "Open for extension, closed for modification" is not a slogan about willpower; it is a structural consequence of routing calls through a polymorphic boundary instead of a conditional.

The mechanism, traced on a real pricing engine

Take a checkout that applies a discount. The naive version branches on a string tag, so every new promotion forces an edit to the one method everyone depends on:

// VIOLATES OCP — every new promo re-opens this method
double priceAfterDiscount(String kind, double total) {
    if (kind.equals("NONE"))        return total;
    else if (kind.equals("FLAT10")) return total - 10;
    else if (kind.equals("PCT20"))  return total * 0.80;
    // add BLACK_FRIDAY? edit here, re-test the whole method...
    throw new IllegalArgumentException(kind);
}

The OCP version pushes each rule behind one stable abstraction. The caller is now closed — adding BlackFriday touches zero existing lines:

interface DiscountPolicy {            // the stable boundary
    double apply(double total);
}

class NoDiscount implements DiscountPolicy {
    public double apply(double total) { return total; }
}
class FlatOff implements DiscountPolicy {
    private final double amount;
    FlatOff(double amount) { this.amount = amount; }
    public double apply(double total) { return Math.max(0, total - amount); }
}
class PercentOff implements DiscountPolicy {
    private final double pct;          // 0.20 == 20% off
    PercentOff(double pct) { this.pct = pct; }
    public double apply(double total) { return total * (1 - pct); }
}

// CLOSED: this never changes when a new policy is added
class Checkout {
    double priceAfterDiscount(DiscountPolicy policy, double total) {
        return policy.apply(total);
    }
}

Go, identical shape — a small interface and value structs:

type DiscountPolicy interface{ Apply(total float64) float64 }

type NoDiscount struct{}
func (NoDiscount) Apply(t float64) float64 { return t }

type FlatOff struct{ Amount float64 }
func (f FlatOff) Apply(t float64) float64 {
    if d := t - f.Amount; d > 0 { return d }
    return 0
}

type PercentOff struct{ Pct float64 } // 0.20 == 20% off
func (p PercentOff) Apply(t float64) float64 { return t * (1 - p.Pct) }

func PriceAfterDiscount(p DiscountPolicy, total float64) float64 {
    return p.Apply(total)
}

Why the naive version is wrong: it is not just ugly, it is a correctness and risk hazard. The if/else chain is a single shared mutation point — every team adding a promo edits the same method, re-tests every existing branch, and risks a typo in an unrelated case (forget the final throw and an unknown tag silently returns nothing). The polymorphic version makes the branch structural: the JVM/Go runtime selects the implementation by type, so a wrong policy is a compile-time type error, not a runtime fall-through.

Step-by-step dispatch trace

Call checkout.priceAfterDiscount(new PercentOff(0.20), 250.0):

StepWhat happensValue
1Caller holds a DiscountPolicy reference; static type is the interfaceref → PercentOff{pct=0.20}
2Runtime reads the object's vtable/itable, resolves apply to PercentOff.applydispatch target fixed
3Body runs total * (1 - pct)250.0 * 0.80
4Returns to the unchanged Checkout200.0

Now ship a Black Friday rule. You add one class and wire it where the policy is constructed (a factory or config) — the highlighted boundary below is the only code that ever learns the new name; Checkout is untouched:

class BlackFriday implements DiscountPolicy {        // NEW FILE
    public double apply(double total) {
        double afterPct = total * 0.70;             // 30% off
        return afterPct > 200 ? afterPct - 25 : afterPct; // +$25 over $200
    }
}
// apply(250.0): 250*0.70 = 175.0; 175 > 200? no → 175.0
diagram
diagram

Pitfalls a working engineer hits

When to reach for OCP — and when not to

The decision is really polymorphic dispatch vs. a conditional. Both produce correct prices; they differ in who pays the maintenance cost.

Signals that point to OCP (the interface): the set of variants is open-ended and grows over time; different variants are owned by different teams/plugins; you want each rule independently unit-tested and shippable; the variation is a first-class business concept ("a promotion") worth a name.

Signals that point to a plain switch / if-else: the cases are fixed and few (an enum of three you control); the logic per case is one line; everything lives in one module; the cases must be handled exhaustively and you want the compiler to flag a missing one (a sealed-type switch gives you that — an open interface does not).

Trade-offs vs. the conditional: OCP buys you a closed, never-re-tested core and pluggable variants; it costs an extra type per variant, a layer of indirection that obscures control flow in a stack trace, scattered logic (you can no longer read all rules in one place), and a virtual-call instead of an inlined branch. The conditional is denser and faster to read for a fixed set, but every new case re-opens shared code.

One-liner: choose OCP polymorphism when variants are open-ended and independently owned; prefer a switch (ideally over a sealed type) when the cases are a small fixed set you control and want exhaustively checked.

Concrete call: for the three built-in discounts above, a sealed-type switch is fine. The moment marketing files "let third-party partners ship their own promo logic," the variant set is now open and partner-owned — that is the signal to commit to the DiscountPolicy boundary.

How OCP actually rests on the other SOLID principles

The relation is not decorative — OCP is load-bearing on three of the others, and the pricing example shows each:

So the chain reads: SRP carves the seam → DIP points the caller at the abstraction across it → LSP keeps every implementation honest → and the emergent property is OCP: extend by adding, never by editing.

Takeaways


Re-authored and deepened for this guide. Sources: Robert C. Martin, Agile Software Development: Principles, Patterns, and Practices (the origin of the SOLID acronym) and Clean Architecture; Bertrand Meyer, Object-Oriented Software Construction (the original Open/Closed formulation); Barbara Liskov & Jeannette Wing, "A Behavioral Notion of Subtyping" (the substitutability contract OCP depends on). Worked discount-policy example, dispatch trace, diagram, pitfalls, and the OCP-vs-switch trade-off authored for this page.

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

Stuck on Final Thoughts on the OpenClosed Principle and Its Relation to Other SOLID Principles? 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 **Final Thoughts on the OpenClosed Principle and Its Relation to Other SOLID Principles** (OO & Low-Level Design) and want to truly understand it. Explain Final Thoughts on the OpenClosed Principle and Its Relation to Other SOLID Principles 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 **Final Thoughts on the OpenClosed Principle and Its Relation to Other SOLID Principles** 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 **Final Thoughts on the OpenClosed Principle and Its Relation to Other SOLID Principles** 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 **Final Thoughts on the OpenClosed Principle and Its Relation to Other SOLID Principles** 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