Open-Closed Principle

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

When the requirements of an application changes, if the application confirms to OCP, we can extend the existing modules with new behaviours to satisfy the changes (Open for extension). Extending the behaviour of the existing modules does not result in changes to the source code of the existing modules (Closed for modification). Other modules that depends on the extended modules are not affected by the extension. Therefore we don’t need to recompile and retest them after the change. The scope of the change is localised and much easier to implement.

The key of OCP is to place useful abstractions (abstract classes/interfaces) in the code for future extensions. However it is not always obvious that what abstractions are necessary. It can lead to over complicated software if we add abstractions blindly. I found Robert C Martin’s “Fool me once” attitude very useful. I start my code with minimal number of abstractions. When a change of requirements takes place, I modify the code to add an abstraction and protect myself from future changes of the similar kind.

I recently implemented a simple module that sends messages and made a series of changes to it afterward. I feel it is a good example of OCP to share.

At the beginning, I created a MessageSender that is responsible to convert an object message to a byte array and send it through a transport.

After the code was deployed to production, we found out that we sent messages too fast that the transport cannot handle. However the transport was optimised for handling large messages, I modified the MessageSender to send messages in batches of size of ten.

The solution was simple but I hesitated to commit to it. There were two reasons:

MessageSender class need to be modified if we change how messages are batched in the future. It violated the Open-Closed Principle.

MessageSender had secondary responsibility to batch messages in addition to the responsibility of convert/delegate messages. It violated the Single Responsibility Principle.

Therefore I created a BatchingStrategy abstraction, who was solely responsible for deciding how message are batched together. It can be extended by different implementations if the batch strategy changes in the future. In another word, the module was open for extensions of different batch strategy. The MessageSender kept its single responsibility that converting/delegating messages, which means it does not get modified if similar changes happen in the future. The module was closed for modification.

The patch was successful, but two weeks later we figured out that we can batch the messages together in time slices and overwrite outdated messages with newer version in the same time slice. The solution was specific to our business domain of publishing market data.

More importantly, the OCP showed its benefits when we implemented the change. We only needed to extend the existing BatchStrategy interface with an different implementation. We didn’t change a single line of code but the spring configuration file.

* For the sake of simplicity, I left the message coalsecing logic out of the example.

Conclusion:
The Open-Closed Principle serves as an useful guidance for writing good quality module that is easy to change and maintain. We need to be careful not to create too many abstractions prematurely. It is worth to defer the creation of abstractions to the time when the change of requirement happens. However, when the changes strike, don’t hesitate to create an abstraction and make the module to confirm OCP. There is a great chance that a similar change of the same kind is at your door step.