Confusion about open/closed principal

684 views Asked by At

Open/closed principle states that classes are closed for modifications but open for extensions. Lets say we want to design a payment system where payment can be processed by multiple processors like following:

    class Payment {
        void pay(paymentMethod) {
            switch (paymentMethod) {
               case 'PayPal':
               break;
               case 'Swift':
               break;
               default:
               break;
            }
        }
    }

    class PayPal {
        void pay() {
               //implement payment1
        }
    }

    class Swift {
        void pay() {
               //implement payment2
        }
    }

Let's say we implement both payment systems the first time. Now if for some reason any payment system implementation process is changed, should not we have to modify the relevant class? For example, if we implement PayPal and after 2-3 years PayPal's working process is changed, does not modifying the PayPal class break the open/closed principle? If it does what's the solution?

3

There are 3 answers

5
Linda Paiste On BEST ANSWER

Having that switch statement in your Payment classes breaks the open/closed principle because it makes the abstract idea of a Payment tightly coupled to the concrete implementations PayPal and Swift. In order to add a remove a supported payment type, you would have to edit the Payment.pay() method.

A better design uses an interface to describe what a payment provider should look like. In this case, that it must have a void pay() method.

Instead of taking a paymentMethod argument as a string, Payment.pay() should accept an instance of a class which implements the payment provider interface. It can call paymentMethod.pay() to execute the correct function. (Depending on your actual setup, it's probably better to pass this argument to the constructor than to a method).

This way it becomes trivially easy to add or remove payment providers because the Payment class does not need any knowledge whatsoever about which provider classes exist.

interface PaymentProvider {
    void pay();
}

class Payment {
    void pay(paymentMethod: PaymentProvider) {
         paymentMethod.pay();
}

class PayPal implements PaymentProvider {
    void pay() {
        //implement payment1
    }
}

class Swift implements PaymentProvider {
    void pay() {
        //implement payment2
    }
}
0
Arsen On

Now if for some reason any payment system implementation process is changed, should not we have to modify the relevant class? For example, if we implement PayPal and after 2-3 years PayPal's working process is changed, does not modifying the PayPal class break the open/closed principle?

No, it doesn't break it. If the working process of PayPal changes it has to be reflected in the class that extends PayPal payment method.

I'll give you an example: Let's say that tomorrow you want to add one more payment method - Transferwise now the pattern says to us that "software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification" meaning that if you need to modify any existing class in order to add new payment method you're breaking an open/closed principle and on the other side if you can just extend PaymentMethod in your new Transferwise class you are extending your system without any change and you are complying with the pattern

0
jaco0646 On

I think the question here boils down to the definition of the Open/Closed Principle. Specifically, does it really mean that code should never change after it's written?

While many people (myself included) have used that definition as a substitute for the OCP, it's an oversimplification. The OCP was originally published by Bertrand Meyer in Object-Oriented Software Construction. I think the answer to this question can be found in the second edition of the book, beginning on page 60, where some "exceptions" to the OCP are noted.

  • If you have control over the original software and can rewrite it so that it will address the needs of several kinds of client at no extra complication, you should do so.
  • Neither the Open-Closed principle nor redefinition in inheritance is a way to address design flaws, let alone bugs. If there is something wrong with a module, you should fix it — not leave the original as it is and try to correct the problem in a derived module... The Open-Closed principle and associated techniques are intended for the adaptation of healthy modules: modules that, although they may not suffice for some new uses, meet their own well-defined requirements, to the satisfaction of their own clients.

Clearly, Meyer did not intend that legacy code should never be rewritten. If new requirements invalidate part of the existing logic, rewriting it may be a sensible approach and was never meant to be prohibited by the OCP.