Skip to content

// SOLID & Principles · 02

Open/Closed

New variant neededleads toAdd code, not edit it

Extend behaviour by adding code, not by editing what already works.

How it fits together

Open/Closed structureBeforeAfter«client»CheckoutPageCheckoutServicePay(order)switch on PaymentTypeCard / PayPal branchInvoice branchCheckoutServicePay(order)pick handler by typeInvoiceHandlerCharge(order)CardHandlerCharge(order)«interface»PaymentHandlerCanHandle(type)Charge(order)PayPalHandlerCharge(order)Pay(order)by type
uses / callsimplements
  • CheckoutPage uses / calls CheckoutService (Pay(order))
  • CheckoutService uses / calls PaymentHandler (by type)
  • CardHandler implements PaymentHandler
  • PayPalHandler implements PaymentHandler
  • InvoiceHandler implements PaymentHandler

Spot it

  • A switch on type grows with every feature
  • Adding a case means retesting all the others
  • The same if-else chain copied in many places

It costs

Every extension point is an abstraction to maintain; guess the axis wrong and it bends nothing.

In code

public interface IPaymentHandler{    bool CanHandle(PaymentType type);    Task ChargeAsync(Order order, CancellationToken ct);}​public sealed class CheckoutService(IEnumerable<IPaymentHandler> handlers){    public Task PayAsync(Order order, CancellationToken ct)    {        var handler = handlers.Single(h => h.CanHandle(order.PaymentType));        return handler.ChargeAsync(order, ct);    }}​public sealed class InvoiceHandler(InvoiceBook invoices) : IPaymentHandler{    public bool CanHandle(PaymentType type) => type == PaymentType.Invoice;​    public Task ChargeAsync(Order order, CancellationToken ct) => invoices.CreateAsync(order, ct);}
Go deeperThe full explanation and what to ask for · 1 min readExpand

Bertrand Meyer's original version relied on inheritance; the reading popularised by Robert Martin uses abstractions: depend on an interface so new behaviour arrives as a new implementation, not an edit to tested code.

Closed does not mean frozen. Bugs still get fixed and code still gets refactored; the principle is about the kind of change you expect to repeat.

You cannot be closed against everything. Add the extension point after the second or third variant shows you which way change actually comes.

Ask for it

  • extension point
  • polymorphism
  • strategy
  • plugin
  • protected variation

Replace the switch on payment type in CheckoutService with a PaymentHandler interface, one implementation per type, all resolved as a collection from the DI container. A new payment type should need only a new class and one registration.