The Open/closed principle (OCP) states that a module should be open to extension but closed for modification. OCP is one of the famous 5 solid principles and an important object-oriented design principle.
1. The Definition
Bertrand Mayer, in his 1988 book, Object-Oriented Software Construction (Prentice Hall), defined the open/closed principle (OCP) as follows:
Software entities should be open for extension, but closed for modification.
Robert C. Martin has more clearly explained the OCP in his book, Agile Software Development: Principles, Patterns, and Practices (Prentice Hall, 2003), as follows:
- Open for extension – This means that the module’s behavior can be extended. As the requirements of the application change, we can extend the module with new behaviors that satisfy those changes. In other words, we are able to change what the module does.
- Closed for modification – Extending the behavior of a module does not result in changes to the source or binary code of the module. The binary executable version of the module, whether in a linkable library, a DLL, or a Java .jar, remains untouched.
2. Open-closed Principle in Practice
To enable the extension of any software module, we can adapt either of two (generally used) mechanisms:
2.1. Implementation-based Inheritance
Implementation inheritance uses the abstract classes and methods. We can define the extension points as abstract methods.
An abstract class can be extended by a few classes with predefined implementations for the most common scenarios. The developer must extend the abstract class and provide that specific implementation logic for client-specific scenarios. This will help in following the OCP.
The template method pattern is a great fit for these usecases. This pattern makes general steps customizable because of delegation to abstract methods. In effect, the base class delegates the individual steps of the process to subclasses.
2.2. Interface-based Inheritance
The client’s dependency on classes is replaced with interfaces. This is, infact preferred approach over abstract methods. This echoes the advice to prefer composition over inheritance and to keep inheritance hierarchies shallow, with few layers of sub-classing.
Design for inheritance or prohibit it. – Effective Java (Addison-Wesley, 2008), Joshua Bloch
3. Open-closed Principle in Action
If you want to see the open-closed principle real-world example, look at the Spring framework. Spring is designed and implemented so beautifully that we can extend any part of its features and inject our custom implementation out of the box. It is very well time tested and working flawlessly as today also.
Let us understand the OCP principle with a hands-on exercise. We will develop a Report generation module that can generate execl and pdf reports. Then we will try to add a new report type, and we will see that if we are able to add the capability without modifying the existing code.
3.1. Non-compliant Code
The Reportable interface represents the parent of all possible report types. Current it has two implementations: ExcelReport and PdfReport. Any new report type must implement the Report interface.
public interface Reportable {
}
class ExcelReport implements Reportable {
Object[][] data;
public ExcelReport(Object[][] data) {
this.data = data;
}
}
class PdfReport implements Reportable {
Object[][] data;
public PdfReport(Object[][] data) {
this.data = data;
}
}
Next, we have ReportGenerator class that generates the reports based on the actual type of Reportable instance.
public class ReportGenerator {
public void generate(Reportable report) {
if(report instanceof ExcelReport) {
//create excel report
} else if(report instanceof PdfReport) {
//create pdf report
}
}
}
And the following program demonstrates, how to create a Reportable instance and generate report for it.
public class Main {
public static void main(String[] args) {
ReportGenerator generator = new ReportGenerator();
generator.generate(new PdfReport(somedata));
generator.generate(new ExcelReport(somedata));
}
}
Now suppose, we want to add the capability of HTML reports. There is no possible way to modify this code without changing the existing module code in ReportGenerator class and the if-condition in it. This type of code is non-compliant to OCP principle.
3.2. OCP-compliant Code
Remember the rule that we should abstract the functionality that changes in the application. In our example, the if-condition and check for Reportable type is changing with each new Reportable type. We must abstract this functionality.
The solution is to delegate the responsibility of report generation to each Reportable itself. Each Reportable instance must have its own logic of report generation and should not depend on the module code for this. See how the modified code looks now.
public interface Reportable {
public void generate();
}
class ExcelReport implements Reportable {
Object[][] data;
public ExcelReport(Object[][] data) {
this.data = data;
}
@Override
public void generate() {
//generate excel report
}
}
class PdfReport implements Reportable {
Object[][] data;
public PdfReport(Object[][] data) {
this.data = data;
}
@Override
public void generate() {
//generate pdf report
}
}
The ReportGenerator has abstracted the report generation to each Reportable types and it merely does the preprocessing and postprocessing.
public class ReportGenerator {
public void generate(Reportable report) {
//pre-processing
report.generate();
//post-processing
}
}
The report generation logic remains same.
ReportGenerator generator = new ReportGenerator();
generator.generate(new PdfReport(somedata));
generator.generate(new ExcelReport(somedata));
With this module code, we can add more Reportable types without affecting the existing module code. For example, we can add the HTML report capability with the following code:
class HtmlReport implements Reportable {
Object[][] data;
public HtmlReport(Object[][] data) {
this.data = data;
}
@Override
public void generate() {
//generate html report
}
}
To execute an HTML report, we can use the generator in conventional method:
generator.generate(new HtmlReport(somedata));
4. Conclusion
The open/closed principle is a guideline for the overall design of classes and interfaces and how developers can build code that allows changes over time. Now when most organizations are adopting agile practices, with each passing sprint, new requirements are inevitable and should be embraced. If the code we have produced is not built to enable change, change will be difficult, time-consuming, error-prone, and costly.
By ensuring that our code is open to extension but closed to modification, we effectively disallow future changes to existing classes and assemblies, which forces programmers to create new classes that can be plugged into as extension points.
A suggested approach is to identify parts of the requirements that are likely to change or that are particularly troublesome to implement and factor these out behind extension points.
Happy Learning !!
Hii Lokesh, i appreciate your hard work to put every article simple. I appreciate the way u explained all the design patterns putting your experience on design patterns in a simple clear way.
I am clear about Open/close principle.
I work on spring boot web based projects. I prefer module based packaging over layer based packing so all the packages contains classes only related to that module feature.
I keep no dependency between packages so that it helps me to change the class code of any package in future easily because there is no dependency between packages.
After reading open close principle i got a lot of doubts in me. I used to follow adding new feature directly to existing classes of controller, service and repository. I need your clarification on below points to clear my doubts
As u already know we write controllers, services and repositories separately, each class contains multiple methods. we don’t write a separate class which contain only single method which perform add operation and one more class which perform get operation to perform crud operations or business logics separately.
1. How can i follow open/close principle when i have a new requirement to add new functionality to the module feature do i need to write a separate controller class service class and spring data repository ?
Ex: user management module earlier i provided only add and get functionality and released to production. Know the requirement is to provide delete operation of user do i need to create a separete classes of all controller, service etc. Then extend it to previous controller, service class and add new functionality to newly created controller.
2. what i should do when a new requirement comes to provide delete functionality to delete user in user management module. How to take up this new requirement and write a code for all layers which follows open/close principle. so that my code neer breaks any other code.
3. What is the best way in spring MVC layer based application world to follow this Open/close Principle can u expain with exmpale in detail so it clarifies many developers.
can u expain in detail these above mentioned points with example.
Thank you in advance, I appreciate your work on sharing your knowledge wisdom.