principios-solid-ocp-abierto-cerrado

Open/Closed Principle (OCP): What it is and How to Apply It

  • 4 min

The Open/Closed Principle consists of adding behavior without modifying existing code.

We’ve arrived at the second letter of SOLID, the O: the Open/Closed Principle (OCP).

This principle, originally formulated by Bertrand Meyer in 1988, presents an apparent paradox that can be confusing at first. Its definition states:

“Software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification.”

Open and closed at the same time? Although it might seem contradictory, the principle states that we should be able to add functionality without touching the original code.

  • Open for extension: We can add new behaviors or functionalities.
  • Closed for modification: We don’t need to alter code that is already written, tested, and working every time we add a behavior.

Every time we modify an existing class, we risk introducing new bugs in functionalities that were previously working fine. The OCP aims to minimize that risk.

The Symptom: The Infinite switch

The easiest way to detect that you are violating the OCP is when you see a class full of if/else or switch statements checking the type of an object to decide what to do.

Suppose we have a PaymentProcessor class. At the beginning, we only accepted credit cards. Then the boss wanted PayPal. And then Bitcoin.

The Bad Design

public enum PaymentType { CreditCard, PayPal, Bitcoin }

public class PaymentProcessor
{
    public void Process(PaymentType type, double amount)
    {
        if (type == PaymentType.CreditCard)
        {
            // Logic for charging the card...
            Console.WriteLine("Charging with Credit Card...");
        }
        else if (type == PaymentType.PayPal)
        {
            // Logic for connecting to the PayPal API...
            Console.WriteLine("Redirecting to PayPal...");
        }
        else if (type == PaymentType.Bitcoin)
        {
            // Wallet logic...
            Console.WriteLine("Blockchain transaction...");
        }
    }
}
Copied!

What’s the problem here? If tomorrow we want to add “Bizum” or “Stripe”, we have to:

  1. Modify the enum.
  2. Open and modify the PaymentProcessor class.
  3. Add a new else if.
  4. Recompile and retest the entire class (making sure we haven’t accidentally broken the Credit Card payment).

This class is not closed to modification. Each new feature involves open-heart surgery.

The Solution: Polymorphism

To comply with the OCP, we use one of the pillars of OOP: polymorphism (generally through interfaces or abstract classes).

Instead of the PaymentProcessor asking “what are you?”, we will simply tell the payment object: “Process the payment”.

We create an interface (or abstract class) that defines the contract.

public interface IPaymentMethod
{
    void ProcessPayment(double amount);
}
Copied!

Each payment method is now a separate class. This also helps with SRP (each class handles its own logic).

public class CreditCardPayment : IPaymentMethod
{
    public void ProcessPayment(double amount)
    {
        Console.WriteLine("Charging with Credit Card...");
    }
}

public class PayPalPayment : IPaymentMethod
{
    public void ProcessPayment(double amount)
    {
        Console.WriteLine("Redirecting to PayPal...");
    }
}
Copied!

Now we rewrite our processor. Notice the change:

public class PaymentProcessor
{
    public void Process(IPaymentMethod method, double amount)
    {
        // The processor does NOT know what type of payment it is.
        // It only knows it fulfills the interface.
        method.ProcessPayment(amount);
    }
}
Copied!

What Have We Achieved?

Now, if we want to add Bizum, we only need the following:

  1. Create a new class BizumPayment : IPaymentMethod.
  2. Implement its logic.
  3. And that’s it!

We don’t have to touch a single line of PaymentProcessor.

  • The system is Open for extension: We added Bizum.
  • The system is Closed to modification: PaymentProcessor has not changed.
// Usage example
var processor = new PaymentProcessor();

// We can pass it anything that is IPaymentMethod
processor.Process(new CreditCardPayment(), 100);
processor.Process(new BizumPayment(), 50);    // It works without touching the processor!
Copied!

Warning: Don’t Obsess

Like everything in engineering, the OCP is not free. It introduces complexity.

If we apply OCP to everything, we’ll end up with millions of tiny interfaces and a “class explosion”.

Apply OCP only in the parts of your system that are likely to change frequently.

If a piece of code hasn’t changed in two years, you don’t need to refactor it to be Open/Closed. Don’t try to predict the future: apply OCP when you see that the change is real.