DEV Community


Open-Closed Principle and Refactoring

alexbunardzic profile image Alex Bunardzic ・3 min read

Bertrand Meyer originally defined Open-Closed Principle in his 1988 book Object Oriented Software Construction:

  • A module will be said to be open if it is still available for extension. For example, it should be possible to add fields to the data structures it contains, or new elements to the set of functions it performs.
  • A module will be said to be closed if it is available for use by other modules. This assumes that the module has been given a well-defined, stable description (the interface in the sense of information hiding).

A more succinct way to put it would be:

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

Similarly (and in parallel to Meyer's findings),
Alistair Cockburn defined Protected Variation pattern:

"Identify points of predicted variation and create a stable interface around them."

Both principles/patterns deal with volatility in software. When, as is sooner or later always the case, we need to make some changes to any software module, the ripple effects could be disastrous. The root cause of disastrous ripple effects is tight coupling, so Open-Closed Principle and Protected Variation Pattern teach us how to properly decouple various modules, components, functions and such.

Does Open-Closed Principle preclude refactoring?

If our module (i.e. named block of code) must remain closed for any modifications, does that mean that we're not allowed to touch it once it gets deployed? And if yes, wouldn't that eliminate any possibility of refactoring?

Without the ability to refactor the code, we are forced to adopt the finality principle. This principle holds that rework is not allowed (why would stakeholders agree to pay us to work again on something they already paid for?), and that we are to carefully craft our code, because we will get only one chance to do it right. Which is in total contradiction with the discipline of refactoring.

If we are only allowed to extend the deployed code, but not change it, are we doomed to forever swim in the waterfall rivers? Being given only one shot at doing anything is a recipe for disaster.

Let's review the approach on how to solve this conundrum.

How to protect clients from unwanted changes?

Clients (meaning modules/functions that use some block of code) utilize some functionality by adhering to the protocol as originally implemented in the component/service. However, as the component/service inevitably changes, the original 'partnership' between the service/component and various clients breaks down. Clients 'discover' the change by breakage, which is always an unpleasant surprise that often ruins the initial trust.

Clients must be protected from those breakages. The only way to do so is by introducing a layer of abstraction in-between the clients and the service/component. In software engineering lingo, we call that layer of abstraction an 'interface' (or an API).

Interfaces/APIs serve to hide the implementation. Once we arrange for a service to be delivered via an interface or an API, we free ourselves from the worries of changing the implementation code. No matter how much we change the implementation of the service, our clients remain blissfully unaffected.

That way, we are back to our comfortable world of iterations. We are now completely free to refactor, to rearrange the code (and to keep rearranging it in pursuit of more optimal solution).

What in this arrangement remains closed for modification is the interface/API. It is the volatility of an interface/API that threatens to break the established trust between the service and its clients. Interfaces/APIs must remain open for extension. And that extension happens behind the scenes -- by refactoring and adding new capabilities while guaranteeing non-volatility of the public-facing protocol.

How to extend capabilities of services?

While services remain non-volatile from the perspective of their clients, they also remain open for business when it comes to enhancing their capabilities. Such Open-Closed Principle get s implemented via refactoring.

For example, if the first increment of the OrderPayment service offers mere bare bones capabilities (only able to process order total and calculate sales tax), the next increment can be safely added by respecting the Open-Closed Principle. Without breaking the handshake between the clients and the OrderPayment service, we can refactor the implementation behind the OrderPayment API by adding new blocks of code.

So the second increment could now contain the ability to calculate shipping cost. And so on, you get the picture, we accomplish Protected Variation pattern by observing the OpenClosed Principle. It's all about careful modelling our business abstractions.


Editor guide