DEV Community

Sathish
Sathish

Posted on • Originally published at sathishsaravanan.com

Open/Closed Principle in C#: Making Your Code Flexible Without Breaking Stuff

Ever built a feature that worked great—until someone added a small change and it all fell apart?

Welcome to the pain of code that violates the Open/Closed Principle.

Software entities should be open for extension, but closed for modification.

It’s the “O” in SOLID, and it’s all about writing code that can grow new behaviors without you having to rewrite what already works.

Let’s unpack that—with real C# code, not vague theory.

Our Old Friend: The InvoiceProcessor

We’ll pick up from where we left off in the SRP post. Here's a class that's responsible for calculating invoice totals:

public class InvoiceCalculator
{
    public decimal CalculateTotal(Invoice invoice)
    {
        decimal total = 0;
        foreach (var item in invoice.LineItems)
        {
            total += item.Price * item.Quantity;
        }
        return total;
    }
}
Enter fullscreen mode Exit fullscreen mode

All good. But now your product manager wants to add discounts. Tomorrow, someone else might ask for tax rules. Next week? Promotional pricing based on customer loyalty.

Do we keep modifying this method every time?

🚨 The Problem: Change Means Risk

If you change this class for every new pricing rule, a few things happen:

  • You risk breaking existing logic.
  • You have to re-test everything.
  • Your code becomes a jungle of if statements.

That’s not sustainable.

Enter the Open/Closed Principle

Rather than adding new logic inside the class, we extend behavior from the outside—through abstraction and composition.

So instead of modifying InvoiceCalculator, we give it a way to plug in pricing strategies.

🏗️ Refactoring for Extensibility

Let’s define a new interface:

public interface IPricingRule
{
    decimal Apply(Invoice invoice, decimal currentTotal);
}
Enter fullscreen mode Exit fullscreen mode

Then we create a base calculator that supports rule injection:

public class FlexibleInvoiceCalculator
{
    private readonly List<IPricingRule> _pricingRules;

    public FlexibleInvoiceCalculator(List<IPricingRule> pricingRules)
    {
        _pricingRules = pricingRules;
    }

    public decimal CalculateTotal(Invoice invoice)
    {
        decimal total = invoice.LineItems
            .Sum(item => item.Price * item.Quantity);

        foreach (var rule in _pricingRules)
        {
            total = rule.Apply(invoice, total);
        }

        return total;
    }
}
Enter fullscreen mode Exit fullscreen mode

Now let’s add a discount rule:

public class TenPercentDiscountRule : IPricingRule
{
    public decimal Apply(Invoice invoice, decimal currentTotal)
    {
        return currentTotal * 0.9m;
    }
}
Enter fullscreen mode Exit fullscreen mode

And another for tax:

public class TaxRule : IPricingRule
{
    public decimal Apply(Invoice invoice, decimal currentTotal)
    {
        return currentTotal * 1.05m; // 5% tax
    }
}
Enter fullscreen mode Exit fullscreen mode

Here’s how you’d use the FlexibleInvoiceCalculator with both the discount and tax rules applied:

// Example invoice setup
var invoice = new Invoice
{
    LineItems = new List<LineItem>
    {
        new LineItem { Price = 100, Quantity = 2 }, // $200
        new LineItem { Price = 50, Quantity = 1 }   // $50
    }
};

// Define pricing rules
var pricingRules = new List<IPricingRule>
{
    new TenPercentDiscountRule(), // 10% off
    new TaxRule()                 // Add 5% tax
};

// Create calculator with rules
var calculator = new FlexibleInvoiceCalculator(pricingRules);

// Calculate final total
decimal finalTotal = calculator.CalculateTotal(invoice);

Console.WriteLine($"Final Total: {finalTotal:C}"); // Output: Final Total: $198.45
Enter fullscreen mode Exit fullscreen mode

You can mix, match, and inject these rules without touching the calculator itself.

Why This Works

Your core logic (the calculator) is closed for modification. You’re not touching its internals anymore.

But it’s open for extension—you can pass in any rule that implements IPricingRule.

This means:

  • ✅ New logic = new classes, not risky edits.
  • ✅ Old logic stays safe.
  • ✅ Behavior is pluggable, testable, and isolated.

🔍 Real-World Benefits

The Open/Closed Principle helps you:

  • ✨ Add features faster.
  • 🚫 Avoid regressions.
  • 🔧 Create modular code that adapts to new requirements without breaking old ones.
  • ✨ Encourage team collaboration—each rule can be owned/tested by different devs.

🧪 A Simple Test

If adding a new behavior means editing existing, working code, you’re probably violating OCP.

If you can write new logic without opening up stable code, you’re doing it right.

Final Thoughts

The Open/Closed Principle is about trust.
You trust that your existing logic works, and you want to extend it without messing it up.

Abstraction isn’t overengineering—it’s insurance for your codebase.
When your app grows (and it will), code that’s open for extension and closed for modification will save you from a lot of late-night refactors.

Your code should welcome change like an open door, but guard its core like a vault.

AWS Q Developer image

Your AI Code Assistant

Generate and update README files, create data-flow diagrams, and keep your project fully documented. Built to handle large projects, Amazon Q Developer works alongside you from idea to production code.

Get started free in your IDE

Top comments (0)

Hot sauce if you're wrong - web dev trivia for staff engineers

Hot sauce if you're wrong · web dev trivia for staff engineers (Chris vs Jeremy, Leet Heat S1.E4)

  • Shipping Fast: Test your knowledge of deployment strategies and techniques
  • Authentication: Prove you know your OAuth from your JWT
  • CSS: Demonstrate your styling expertise under pressure
  • Acronyms: Decode the alphabet soup of web development
  • Accessibility: Show your commitment to building for everyone

Contestants must answer rapid-fire questions across the full stack of modern web development. Get it right, earn points. Get it wrong? The spice level goes up!

Watch Video 🌶️🔥

AWS GenAI LIVE!

GenAI LIVE! is a dynamic live-streamed show exploring how AWS and our partners are helping organizations unlock real value with generative AI.

Tune in to the full event

DEV is partnering to bring live events to the community. Join us or dismiss this billboard if you're not interested. ❤️