SOLID Principle Part 2: Open Closed Principle (OCP)

According to the open-closed principle in object oriented programming, software entities (classes, modules, functions, etc.) should be open for extension but closed for modification. In other words, to meet changing needs, we should extend the behavior of software entities without affecting existing code. By using this principle, we can make our system more flexible, maintainable, and scalable.

Let's Understand Open-Closed Principle via Story

After years of development and reading 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: 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. Design is an optimization problem!"  — The wise developer replied.

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.

Change by addition and modification in software code

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."

What is open-closed principle in oops?

“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)


Let's understand Open-Closed Principle via an example

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 behaviours and properties as a base class. And actual behaviours 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 behaviours is:

void attack(Character character) {
    if(character.name == 'Mario') {
        // logic for a mario attack
    } else if(character.name == 'Lugi') {
        // logic for a lugi attack
}

void jump(Character character) {
    if(character.name == 'Mario') {
        // logic for a mario jump
    } else if(character.name == 'Lugi') {
        // logic for a lugi jump
    }
}

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:

abstract class GameCharacter {
    //.. other things
    abstract void attack() {}
    abstract void jump() {}
}

class Mario extends GameCharacter {
    void attack() {
        // logic for a mario attack
    }
    
    void jump() {
        // logic for a mario jump
    }
}

class Lugi extends GameCharacter {
    void attack() {
        // logic for a lugi attack
    }
    
    void jump() {
        // logic for a lugi jump
    }
}

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.

Advantages of Open Closed Principle

  • Flexibility: As discussed above, the Open-Closed Principle provides ability to extend or modify the behavior of a system without changing its source code. This makes our code development more flexible and adaptable to changing requirements.
  • Modularity: Based on this principle, software code is divided into separate, interchangeable components that can be updated or extended without affecting the existing software code.
  • Maintainability: The closed nature of the modules ensures that changes to one part of the software code don't affect other parts. This will reduce the likelihood of unintended bugs or complexity and make the system easier to maintain.
  • Reusability: By separating the behavior from the implementation, we can easily reuse software code in other parts of the application.

Summary

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!

More from EnjoyAlgorithms

Self-paced Courses and Blogs