Table of contents
OCP is about not changing production-ready code.
And that, in my opinion, is all I could write about this principle after many hours of research. However, how to achieve such a state of affairs? In this article I deliberately start with more theoretical considerations about this principle, because trivial examples demonstrating the application of it are plenty on the Internet. Nevertheless, at the end I present some examples that I think are more realistic in the everyday art of programming.
What is OCP and how to pursue it
Software entities (classes, modules, functions, etc.) should be open for extension but closed for modification.
Agile principles, patterns, and practices in C#
We satisfy the OCP principle mainly through abstraction supported by a dependency container and dependency injection. We can do this in several ways. The best way is to start with interfaces, because they allow us to freely change the implementation. Then move to abstract classes, where much of the functionality can be shared. Only at the end use the inheritance from other classes, because they are rarely made with a view to being someone’s parent.
The OCP principle can be supported by using the principles of inversion of control and single responsibility. However, as we will see in the examples and in our own practice, it is impossible to use only one of the SOLID principles - they all together create a wall without as many weak points as possible.
I would like to further emphasize that abstraction is never about creating elaborate class trees. We should mainly try to limit ourselves to implementations of as simple interfaces as possible, since their implementations are easiest to replace with a dependency container. In case we want to avoid copying code we often have two options: abstract (base) class and composition, where I recommend the latter in particular.
What’s the best way to go?
Fool me once, shame on you. Fool me twice, shame on me.
Agile principles, patterns, and practices in C#
With this quote, Robert C. Martin means not to apply abstractions too hastily, which can lead to overcomplicated code. So when is the best time to start introducing interfaces and virtual classes? The second time! There was only supposed to be one user type, and now a second one is coming? Make it so that you can add a third easily as well.
The longer we wait to find out what kinds of changes are likely, the more difficult it will be to create the appropriate abstractions.
Agile principles, patterns, and practices in C#
There’s something about the fact that the longer we wait, the more unique the modules seem. They are also composed of small components that can be abstracted away. Given this and the previous golden advice, you might conclude that an appropriate abstraction should be added whenever there is a second component or there is a second change to code already running in production. Importantly: changes, not repairs.
In general, no matter how "closed" a module is, there will always be some kind of change against which it is not closed.
Agile principles, patterns, and practices in C#
Golden thought for inspiration: no matter how hard we try, it won’t be perfect forever. Therefore, don’t work hard to make sure everything is prepared for future changes: it won’t be. Remember that in an application it is not only the resistance to future changes that matters, but also the fact that we have to provide the functions needed by our client.
As Robert C. Martin ([AgilePPP]) writes, be careful not to overdo it with too much abstraction. You may end up overcomplicating the whole thing.
What does OCP give us?
If we take care of production tested code we get several benefits:
-
plugin architecture,
-
easier implementation for juniors,
-
fault-tolerant code,
-
faster deployment of features.
Flexibility - plugin architecture
According to Robert C. Martin, the highest form of OCP are plugins ([theOCP]). As an example, the ubiquitous code editors, browsers, or games can be given entirely new functionality by using extensions (mods). This flexibility gives us the ease of making changes to a program by isolating them in separate modules.
Concreteness in making changes - easier implementation for juniors
It is much easier to "use" junior in a project that consists of simple interfaces, because what can be complicated about implementing such an interface:
public interface ICalcOperation
{
string Name {get;}
double Calc(double left, double right);
}
Leaving aside the sense of this interface, its biggest advantages are first of all transparency: junior knows in which scope he has to do his work. On top of that, he will do his work in separate, new classes, without touching the production code. A more illustrative comparison can be found below.
Robustness - fault-tolerant code
Code becomes fault-tolerant by changing less frequently those pieces of software that are already battle-tested. What’s more, thanks to the clear division between superior classes, responsible for logic, and executive ones (the principle of inversion of control comes to mind), it’s easier to assess who should take care of a possible bug: junior, mid, or maybe senior.
Reusability and transparency - faster feature deployment
By isolating minor functionality, individual pieces of software are more likely to be used in another project. Increasing transparency, thanks to simple interfaces and plugin architecture, allows us to include new functionalities faster, especially in those aspects that have the highest rate of code reuse.
Code example
Now let’s move to an example. Let’s use a trivial model of a calculator, which will be based on the interface presented above. We can do this in two ways: decomposing everything into prime factors or taking OCP into account.
Distributed method
We can write our application in a simple way, like for a college project. What does it look like then?
Step 1: Relocation
Pierwszym krokiem, i często ostatnim, jest przeniesienie poszczególnych funkcjonalności do osobnych klas:
Step 2: Isolation and unification
In this step, we will encapsulate objects to hide the dependencies of individual commands:
Step 3: Interface Implementation
This step is not always mandatory. It involves changing several layers in a way that requires a lot of knowledge about the language and technology being used - making it, without real seniors, potentially impossible for the team. However, sometimes it happens that the requirements for the presentation layer are so specific that unifying this issue is so much work that it is not profitable.
What to watch out for.
Personally, there are two things to be careful about: enums and structured programming in combination with object oriented programming.
Robert C. Martin himself points out the former, saying that he tolerates them only if they are used to create an object and additionally are not accessible from the outside [CleanHandBook].
Furthermore, it is important to note that the use of an enum in more than one set of switch
…case
or if
…else
statements is a great indicator of the location that could be taken care of in order to apply the Open Close Principle.
I find such a split between structured and object oriented programming dangerous for a simple reason: changes in such code are often cascading and extracting the right abstraction is simply hard. It would probably be better to simply write either structured or object oriented code - it is best to just decide.
Sources and additional materials
-
[theOCP] Martin, Robert C. „Clean Coder Blog”. Dostęp z dnia 17 listopada 2021. https://blog.cleancoder.com/uncle-bob/2014/05/12/TheOpenClosedPrinciple.html.
-
[CleanHandBook] Martin, Robert C. Clean Code: A Handbook of Agile Software Craftsmanship. Repr. Robert C. Martin Series. Upper Saddle River, NJ Munich: Prentice Hall, 2012.
-
[AgilePPP] Martin, Robert C., i Micah Martin. Agile Principles, Patterns, and Practices in C#. Robert C. Martin Series. Upper Saddle River, NJ: Prentice Hall, 2007.
-
Samokhin, Vadim. „The Open-Closed Principle”. HackerNoon.Com (blog), 16 czerwiec 2018. https://medium.com/hackernoon/the-open-closed-principle-c3dc45419784.
-
Chovatiya, Vishal. „Open Closed Principle in C++ | SOLID as a Rock”. Vishal Chovatiya, 7 kwiecień 2020. http://www.vishalchovatiya.com/open-closed-principle-in-cpp-solid-as-a-rock/.
-
Azevedo, Gustavo Peixoto de. „The Open/Closed Principle: Concerns about Change in Software Design”. The Sympriser Blog, 23 czerwiec 2009. https://blog.symprise.net/articles/open-closed-principle-concerns-about-change-in-software-design.
-
Stackify. „SOLID Design Principles Explained: The Open/Closed Principle with Code Examples”, 28 marzec 2018. https://stackify.com/solid-design-open-closed-principle/.
Title photo: engin akyurt from Unsplash