Open-Closed Principle – ‘O’ in SOLID Principles

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 !!

Sourcecode on Github

Comments

Subscribe
Notify of
guest
1 Comment
Most Voted
Newest Oldest
Inline Feedbacks
View all comments

About Us

HowToDoInJava provides tutorials and how-to guides on Java and related technologies.

It also shares the best practices, algorithms & solutions and frequently asked interview questions.

Our Blogs

REST API Tutorial

Dark Mode

Dark Mode