The Open/Closed Principle (OCP) states that classes should be open for extension, but closed for modification. The goal is to allow classes to be easily extended to incorporate new behavior without modifying existing code. This means when extending your software you should not need to go and dig around in its internals just to change its behavior. You should be able to extend it by adding to it new classes without the need to change the existing code.
Open to Extension = New behavior can be added in the future
Closed for Modification = Changes to code are not required
But applying the OCP everywhere is wasteful and unnecessary. The OCP leads to more complex designs and to harder understandable code especially for beginners. It is said that the OCP should not be applied at first. If the class is changed, we should accept it. If it changes a second time, we should refactor it to OCP.
Let’s look at an example. Assume we have a web shop where there is a function that calculates the total amount of all items in a shopping cart. As shown in the code below, there are different type of rules how the total amount is calculated depending on the item.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public double totalAmount(List<Items> items) { | |
double total = 0.0; | |
for (item : items) { | |
if (item.getCategory() == "DISCOUNT") { | |
total += 0.95 * item.getPrice(); | |
} | |
else if (item.getCategory() == "WEIGHT") { | |
total += item.getQuantity()*5/1000); | |
} | |
else if (item.getCategory() =="SPECIAL") { | |
total += 0.8*item.getPrice(); | |
} | |
// more rules are coming! | |
} | |
return total; | |
} |
Every time a new rule is added or the way how items are priced is modified requires changes to the class an its method. Each change can introduce bug and requires re-testing. At this point, we know that there are more rules coming. So we must think about how we can refactor this code in such a way that we don’t have to go in and edit this particular method every time. The way that we can introduce new behavior is through new classes. They are less likely to introduce new problems since nothing depends on them yet.
There are typical two approaches in an object-oriented programming language to achieve OCP. The first possibility is using the template pattern. This pattern encompasses an abstract base class that provides a default behavior. Items in our example inherit from this base class and override the default. The second possibility is to use the strategy pattern. This pattern allows to change the class behavior or its algorithm at run time. Maybe, this pattern would be a bit over-engineered in our simple example. However, the resulting calculation can be shortened with both patterns to the following:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public double totalAmount(List<Items> items) { | |
double total = 0.0; | |
for (item : items) { | |
total += item.getPrice(); | |
} | |
return total; | |
} |