#begin 

This is the second blog in the series about the SOLID principles of Object Oriented Design. It is all about the O in SOLID, the Open-Closed Principle. If you missed the first blog in this series, you can read it here.

The Open-Closed Principle

As with some of the other principles that make up SOLID, the open-closed principle (OCP) was not originally invented by Uncle Bob. The OCP was coined by Bertrand Meyer in 1988 in his book: Object Oriented Software Construction[1] and it means the following:

A software artifact should be open for extension but closed for modification.

This essentially means that the behaviour of software must be extendible without having to modify it’s original source. Interestingly, this is the soul reason “soft”ware exists. The word “soft” in software means demanding little work. Hardware on the other hand is “hard”, which means not easy to change. If some new requirements force massive changes to the architecture of the system, the software architects of that system, have failed to do their job.

The OCP is a principle that is often related to software architecture only but not to the lower levels of abstractions like classes. Bertrand Meyer was originally mostly speaking about the OCP in the context of classes. He has some additional things to say about it in his book:

  • 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).

He explains that by making the right base classes, and implementation hierarchies we are always able to add new fields to existing classes. Also, in OOP languages there is the notion of virtual functions and function overriding. We can use that to further extend the behaviour of a specific function. However, always remember not to diverge from it’s intended behaviour. What do I mean? Well have you ever seen those classes where some, or all, virtual functions are stubbed out? Just to get some convenient data or general behaviour yet not fully implement the base class. Another funny thing with function overriding is that sometimes the new implementation has no relation with the base class’ original intend. This is just clever code sharing and in my opinion a violation of the OCP, and from the previous blog; the Single Responsibility principle.

During the 1990’s the OCP started to become popular and got renamed to the “polymorphic open-closed principle”. The community added “polymorphic” since it became popular to start with an abstract baseclass or an interface. Then, we only want to use the abstract base or the interface. Uncle Bob wrote the first large piece about the OCP in the C++ Report in 1996. You can read it here if you are interested. The reason this approach became popular is because, if we use abstraction we can reduce the dependencies our software has. If we only use Bertrand meyer’s approach, we will end up with tight coupling if the subclasses depend on implementation details of their parent class.

Other than reducing coupling to concrete classes and reuse of code, are there any other advantages in using the OCP? Well, yes… We will reduce the chance of creating bugs in the software since we are extending, not modifying existing classes in our system. So, we do not have to modify already tested classes in production, we just extend them, and test the extended behaviour. If we do it right, we just have to test and deploy the new features, instead of testing and deploying the entire system. The OCP just helps us minimizing changes to existing code and promotes the writing of new code.

Impact on Software Architecture

The OCP is really the driving force, the essence, of good software architecture. Again; we want to minimize the changes to existing code and write new code to implement new requirements. We want to make a system that is robust and easy to extend while reducing the impact of change. To achieve this we want to partition our system in components in a correct dependency hierarchy that protects high level components from changes in low level components.

There are many software architectures that promote nicely separated dependency hierarchies. I’ve already talked about these in my previous blog. Any of the Clean/Hexagonal/Union/VerticalSlice/PortsAdapters architectures will definitely help you keeping the architectural boundaries well defined. But, this is no guarantee. Even with these architectures you can violate the OCP. How? Well, if you depend on concrete classes at the boundaries of you system you will most likely violate the OCP. So, you always want interfaces at the boundaries of you system. For the UI, the database, any outside component that is outside the business domain. Why specifically interfaces? Well because with an interface, you can flip a source code dependency and make external components depend on internal components. You remove the source code dependency but keep the run-time dependency, which is a good thing.

Are these the only architectures where compliance with the OCP is possible; No, of course not. Any architecture that defines it’s API’s through interfaces will adhere to the OCP. So you might take a known architectural pattern, or create one yourself. Just remind yourself that compliance with the OCP will save you a lot of trouble.

I will give you a nice, yet not so obvious, and interesting example of violation of the OCP I once created for myself. I once wrote a communication protocol for our Unity3D games to a PHP(yuck) backend. The communication was a simple REST api with data in JSON format. I used Newtonsoft JSON serializer for encoding and decoding. Now, with Newtonsoft you are able to add “type name handling” settings to the encoding and decoding process. This type name handling adds the types, including namespace, to the JSON objects. I thought this was really clever because now I could simply deserialize it and Newtonsoft would generate the correct class for me, with all properties included. There were however, two problems with my approach. One; I directly serialized domain objects and threw them over to the API. So my business objects were now directly being communicated to the backend. The problem here is that I was exposing internals to the backend which we never ever want to do. Second; There is now a concrete dependency between the front-and-backend. If I wanted to change something in the domain, I had to change the backend as well. Also, if I needed changes in the backend, I needed to change my domain object. This is clearly a violation of the OCP.

I solved this particular issue by adding proper request and response objects. These objects were just an abstract representation of the domain object that I previously simply serialized and threw to the API. This gave me much more control of the communication process and I was also able to do some clever optimizations. For example: during deserialization of data coming from our backend I could detect if the data contained any binary files (paths to URL’s) and download them all. No extra passes were needed to do additional parsing of the input, it was all done during the deserialization process.

Impact on other paradigms

Many of the functional programming languages allow some Object Oriented constructs. The most important one here is of course; the interface. If we, for example, look at F# we can use interfaces. This will help code comply with the OCP just like any other typical OOP language would do. However, some languages do not allow or support traditional OOP interfaces. We have to comply with the OCP some other way. We can do this with proper type inheritance, yes this might mean that we comply with the OCP, yet still depend on some concrete base. This is how Bertrand Meyer originally meant for the OCP to work, yet it will not comply with the polymorphic version of the OCP. I do not think this is a major problem, it is just how these programming domains work. Typically, in functional languages you try to avoid the concept of an object all together. You program against types and data structures, yet not against inheritance trees, but towards compositions.

In a language like Haskell, which is a “pure”, functional language there is no concept of an interface. Yet, there are still ways to align with the OCP in some way. There is this concept of the “open recursion model” for inheritance. Open Recursion simply means: Another handy feature offered by most languages with objects and classes is the ability for one method body to invoke another method of the same object via a special variable called self or, in some languages, this. The special behavior of self is that it is late-bound, allowing a method defined in one class to invoke another method that is defined later, in some subclass of the first[3].

Another way to align with the OCP in Haskell might be to make use of lenses. A Lens A B is essentially a way to “access” a small part (of type B) of a larger structure (of type A); we can both get that smaller part and we can modify it. So lenses allow you to make sort-of subclasses where you can access internal state of the inherited class, yet only the public API is visible externally[4].

Conclusion

This was the second of five blogs in the SOLID series. I found it really interesting to do some research about this principle on low levels of abstraction, architecture and the functional paradigm. I understood what this principle meant before writing this post yet I only knew about the polymorphic OCP version. I think the polymorphic version is the way to go since you will not depend on concrete classes. It was very interesting to read about Bertrand Meyer’s definition and search for some examples. I only knew about Uncle Bob’s version (so the polymorphic one) but it was nice to read about them both and see how they differentiate. Then, the impact on the functional paradigm; Many functional languages are of the hybrid kind. They support some, if not all, aspects of OOP and allow functions to be tossed around. With these hybrid languages, it is rather easy to comply with the OCP since you use and implement interfaces. With a language like Haskell it becomes more difficult yet not impossible. The concept of open-recursion and lenses can be used to create some dependency hierarchy. I did not know about these before, so it was really nice to read something about it. Without writing this blog, I would definitely never have thought looking this up in a search engine. That is what these blogs are all about!

References

[1] Object Oriented Software Construction – https://dl.acm.org/doi/book/10.5555/534929

[2] Clean Architecture – https://www.oreilly.com/library/view/clean-architecture-a/9780134494272/ 

[3] Open Recursion – www.comlab.ox.ac.uk/people/ralf.hinze/talks/Open.pdf

[4] Lenses – https://arxiv.org/pdf/1703.10857.pdf

#end

01010010 01110101 01100010 01100101 01101110

Hey, sorry to bother you but you can subscribe to my blog here.

Never miss a blog post!

You have Successfully Subscribed!