After years of development and reading about a dozen different design patterns, Mohan was going through an existential crisis.
“There are just too many of them! And everyone has a different opinion on which pattern should be used under what scenarios. Even after so many years my code still looks messy. Sometimes I feel life was better with procedural code.”— he muttered to himself.
Frustrated, he decided to visit his master again. His Master, The Wise Developer was meditating under a banyan tree with a smile on his face.
Mohan: “Master, Everywhere I see we are adding layers upon layers of abstraction in our code in the name of reusability and maintainability but the truth is the features which used to take days to implement previously are now taking months. New requirements keep coming and the decisions we took 6 months ago are now irrelevant. After so many years of learning, I still can’t seem to understand why do we bother with Designing software at all ?”
The Wise developer: “We are living in an era of rapid change Mohan, and the rate of change is going to increase even further in the future. Every change you make to software is going to add more complexity to your codebase and more complexity will inversely impact the developer velocity. This is the truth, Neither you nor I can change it, we can only accept it the way it is.”
Mohan: “Then why should one bother with Design? If what we write today is going to be outdated within a few months ?”
Because the purpose of Design is not to reduce the quantity of change, but to reduce the cost of change in future. — The wise developer replied
Design is an optimization problem!
Mohan: "Master, can you please throw some light on this idea?"
Good question Mohan, But before going there that you need to understand the different types of changes that a developer can push into the software. And those are
- Change by Addition
Changes where new components/classes are added.
- Change by Modification
Changes where existing components/classes are modified.
The Wise Developer: “Can you determine the impact of these changes on software?”
Mohan: “*Although All changes carry a risk of introducing some defect to the system, Change by modification is riskier, as it can create regressions.”*
And these regressions can be very hard to diagnose because theoretically, they can exist anywhere within the entire system. To prevent that we need to invest resources into regression testing, which takes time. Also, such changes require a developer to have a decent understanding of all the nuances of the existing system, which further slows down the development cycle.
The Wise Developer: “Correct! Whereas Change by Addition is less risky, due to its low probability of creating regressions. And the defects introduced (if any) will be concentrated in the new component that was added, thereby reducing the cost of testing and debugging in future.”
Thus, we come to the following conclusion:
If you design a software in such a way that it naturally allows Change by Addition, you can significantly reduce your cost of change in future.
This idea is popularly known as ‘The Open-Closed Principle’. Which states:
“Software entities should be open for extension but closed for modification.” — Robert C.Martin (The Open-Closed Principle, 1993)
Mohan: “But Master, isn’t there a Contradiction in both the terms? How can software be Open and Closed at the same time?”
The Wise developer: “No Mohan, they are not Contradictory, they are Complementary.”
This is because the principal tries to achieve two goals of different nature.
“When a single change to a program results in a cascade of changes to dependent modules, that program exhibits the undesirable attributes that we have come to associate with “bad” design. The program becomes fragile, rigid, unpredictable and unreusable. The open-closed principle attacks this in a very straightforward way. It says that you should design modules that never change. When requirements change, you extend the behavior of such modules by adding new code, not by changing old code that already works.”
— Robert C.Martin (The Open-Closed Principle, 1993)
Mohan: “Master, I can understand the theory behind it. But how do I use it practically?”
The Wise Developer: *“The key is Abstraction.”*
Using Abstraction, it is possible to represent a set of desirable behaviors and properties as a base class. And actual behaviors can be represented as derived classes. This method allows you to incrementally add new derived classes to cater to changing requirements of the future, with minimal changes to the existing codebase. For example, say you are building a Mario game, with different types of playable game characters. And let's say all of your game characters can attack and jump. One way to code such behaviors is:
Notice that both these methods need to be modified if we want to add another playable character to the game. In other words, these methods are not closed with respect to changes in playable characters. Now consider another approach:
Here, we can easily introduce another character to the game with minimal modification to existing code. In other words, this system conforms to the requirements of the open-closed principle with respect to changes in playable characters of the game.
Food for thought: A completely closed system?
Mohan: "Master, Is it possible to create a completely closed system?"
The Wise Developer: "No Mohan, you can’t."
For example, Let’s say I want my game characters to fly. To achieve that, I will have to update all existing classes in the second approach, But I only need to add one method in the first approach. That is, inheritance is not closed with respect to introducing new behaviours to existing entities. In general, openness and closeness are relative terms and can never be complete. And Since closure cannot be complete, it must be strategic. That is, the developer must choose the kinds of changes against which to close his design.
A wise developer is the one who knows the system well and invokes the open-closed principle for the most probable changes.
To summarize, for a system to be flexible and resilient in this rapidly changing world, we should favor Change by Addition over Change by Modification. Because:
- It has a lower probability of introducing regressions.
- It requires fewer resources in terms of development, debugging, and testing.
This idea is popularly known as The Open-Closed Principle, a term that was coined by Bertrand Meyer, and made popular by Robert C Martin. This principle allows you to use the power of abstraction and inheritance to create systems that are Open For Extension but Closed For Modification.
Enjoy learning, Enjoy oops!