Open Closed Principle in SOLID

Before you dive into this article, make sure to read about the Single Responsibility Principle.

The Open Closed Principle is one of the SOLID principles defined by Robert C. Martin. The principle says “Software entities (classes, modules, functions, etc.) should be open for extension but closed for modification”.

So in simple words, it says that an implementation (of a class or function), once created, should be closed for further modification, in other words, one should not modify an implementation (of a class or function) of logic and/or functionality. One can do refactoring or resolve errors of implementation (of a class or function) but the implementation (of a class or function) is open for extension, in other words, one can extend the implementation (of a class or function) of logic and/or functionality.

Real-Life Example of Open-Closed Principle

An electric adapter is a good example of this principle.

Adapter

Adapter

Adapter

As you can see in the image

  1. An adapter in the wall is always closed for modification, in other words, we cannot change it once it is fitted or extended if we want more.
  2. But an adapter always provides a method of extension, so we can plug in an extension board of an adapter for more adaptation.
  3. So you plug in an extension board and extend an existing electric adapter fitted into the wall.

Example of not following the principle in Application Development

A bank offers various types of savings accounts (Salary Saving, Regular Saving, and so on) that meet the needs of many types of customers. A bank has differing sets of rules and each savings account type has a different set of rules to calculate interest.

To calculate the interest of an account, developers have developed the following class with methods to calculate interest.

Public class SavingAccount  
{  
    //Other method and property and code  
    Public decimal CalculateInterest(AccountType accountType)  
    {  
        If(AccountType=="Regular")  
        {  
            //Calculate interest for regular saving account based on rules and   
            // regulation of bank  
            Interest = balance * 0.4;  
            If(balance < 1000) interest -= balance * 0.2;  
            If(balance < 50000) interest += amount * 0.4;  
        }  
        else if(AccountType=="Salary")  
        {  
            //Calculate interest for saving account based on rules and regulation of   
            //bank  
            Interest = balance * 0.5;  
        }  
    }  
}  

So in the preceding code, the SavingAccount class's CalculateInterest method does a calculation based on the account type like Salary and Regular.

So the implementation is not following the Open Closed principle because if tomorrow the bank introduces a new SavingAccount type then there is a requirement to modify this method for adding a new case for the new account type. An example is if the bank introduces a “Child Savings Account type” requiring a new condition for calculating the interest for this account type. That means that the method is always open for modification.

One more thing to note here is that the method is also not following the Single Responsibility Principle. Here the method is doing more than one thing, like calculating interest for more than one type.

How to Implement the Open-Closed Principle?

Inheritance is only one way to implement the Open Closed Principle. Because inheritance is only an Object Oriented Design (OOD) basic pillar that allows the extension of the functionality of an existing class.

To implement the Open Closed Principle one can use an interface, an abstract class, abstract methods, and virtual methods then inherit them when you wants to extend functionality.

So the preceding problem of a Savings Account can be resolved in the following.

Saving account

Interface ISavingAccount  
{  
   //Other method and property and code  
   decimal CalculateInterest();  
}  
Public Class RegularSavingAccount : ISavingAccount  
{  
  //Other method and property and code related to Regular Saving account  
  Public decimal CalculateInterest()  
  {  
    //Calculate interest for regular saving account based on rules and   
    // regulation of bank  
    Interest = balance * 0.4;  
    If(balance < 1000) interest -= balance * 0.2;  
    If(balance < 50000) interest += amount * 0.4;  
  }  
}  
  
Public Class SalarySavingAccount : ISavingAccount  
{  
  //Other method and property and code related to Salary Saving account`  
  Public decimal CalculateInterest()  
  {  
    //Calculate interest for saving account based on rules and regulation of   
    //bank  
    Interest = balance * 0.5;  
  }  
}  

In the preceding code two new classes are created, RgularSavingAccount and SalarySavingAccount, that is inherited from IsavingAccount.

So if there is a new account added by the bank then there is no need to modify the logic of exiting classes, just extend the functionality by inheriting an interface.

Finally, the preceding example implements the Open Closed Principle since there is no need to modify the existing implemented logic and it allows extension for adding new logic.

The preceding example also implements the Single Responsibility Principle since each class or function is doing only one task.

Note. An interface is created here just as an example. There could be an abstract class of SavingAccount that is implemented by a new savings account type.

Disadvantage of not following Open Closed Principle

  1. Since a class or function always allows the addition of new logic, whenever new logic is added it is always necessary to test for full functionality. That requires the addition of a new test case for the added functionality and might also require the modification of an existing test case that fails because of added functionality.
  2. It also breaks the Single Responsibility Principle since a class or function might end up doing multiple tasks.
  3. Class or function maintenance becomes difficult since a class or function can become thousands of lines of code that are difficult to understand.

How to identify not following the Open Closed Principle?

  • A class or function is always open for modification, in other words always allows adding more logic to it. Like in the preceding example.

Final Thoughts

Both Single Responsibility and Open Closed Principle highly depend on each other. If one breaks then others break. One should always think when creating the design of the system.

Further Read