The Power of the Open-Closed Principle
In today’s instalment, we’ll dive deep into the Open-Closed Principle (OCP). It’s the second of the famous SOLID Principles—the O in the SOLID acronym refers to the OCP. As a recap, here is a high-level explanation of the SOLID Principles’ purpose.
The OCP is one of my favourite SOLID Principles. It promotes the building of pluggable systems; i.e. systems where we may replace modules and components with counterparts offering similar yet different capabilities.
The Open-Closed Principle:
A software artefact should be open for extension
but closed to modification.
The beauty of the OCP lies in its simple message: When we want to change the behaviour of our software systems, we would rather write new code than modify existing code.
I think this makes a lot of sense. There exists a substantial flaw in the system architecture if every time we want to add a minor extension to the system, we must make significant changes to our existing code. The OCP encourages us to structure systems in such a way that we rarely need to touch existing code—ideally never. That ideal is rarely achievable; there always come times when we must change code. However, at least we can aim for minimal code changes.
The OCP counteracts Software Rigidity. A rigid system is one that is difficult to change. If we follow the OCP and manage to write only new code for a requirement, how does rigidity affect us? It doesn’t—we have sidestepped a code churn snakepit.
Pluggable systems are the result of the OCP. In a pluggable system, we can easily unplug existing functionality by plugging in new functionality.
I have found that the OCP is often best explained in terms of violations and how to fix them. Say, we have this code in our application:
var paypal= new PaypalPaymentGateway(paypalConfig); paypal.Pay(paypalCart, creditCard);
If we wanted to use a different payment gateway, Stripe, then we have little choice but to replace the code to suit Stripe:
var stripe = new StripePaymentGateway(stripeConfig); stripe.BasicPay(stripeCreditCard, creditCart);
Since we had to change these calls; we had to modify the existing code. We are not aligned with the OCP.
Can we do better? Probably.
It would be helpful if we could have a flexible payment gateway reference that could be either a PaypalPaymentGateway
or StripePaymentGateway
.
It is possible.
Firstly, let’s create an interface, IPaymentGateway
, which has a Pay()
method:
public interface IPaymentGateway { void Pay(ShoppingCart cart, CreditCard card); }
Both the PaypalPaymentGateway
and StripePaymentGateway
classes implement IPaymentGateway
.
Consequently, the code to pay using either of our payment gateways becomes
PaymentGateway.Pay(shoppingCart, creditCard);
where PaymentGateway
is a reference of type IPaymentGateway
.
Conclusion
We have made it so the payment gateway is pluggable into the calling code; this code actually knows nothing about either Paypal or Stripe!
To make the software work for yet another payment gateway, say, Braintree, will mean adding new code, a BraintreePaymentGateway
gateway class which implements IPaymentGateway
.
(For the curious reader, here is a more detailed and dynamic version of this payment gateway example.)
Continue reading about the OCP: The OCP Looks Into The Future
Leave a Reply
Want to join the discussion?Feel free to contribute!