Search code examples
oopdesign-patternssolid-principlesopen-closed-principle

Confusion about open/closed principal


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?


Solution

  • 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
        }
    }