Open In App

Open Closed Design Principle in Java

Last Updated : 17 Jan, 2023
Improve
Improve
Like Article
Like
Save
Share
Report

Open Closed Design Principle is one of the most important design principles in the world of software development. It’s part of a group of 5 design principles commonly known by the acronym “SOLID”

S - Single Responsibility Principle
O - Open Closed Principle
L - Liskov Substitution Principle
I - Interface Segregation
D - Dependency Inversion

As per Robert C Martin, this principle states that software entities (classes, modules, functions) should be open for extensions, closed for modifications. This means that a class should be flexible enough to accommodate changes when business requirements change without breaking the existing code. Thus, the dependent classes do not have to change. This minimizes the testing effort and we only need to focus on new code changes.

Methods:

There are basically two primary ways of implementing the interfaces:

  1. Using inheritance
  2. Using Interfaces and abstract methods

Method 1: Using inheritance 

One way to follow this principle is using inheritance. We have a superclass that is closed for modifications, and it is baselined, and part of a jar file.  Anybody who wants to modify the superclass behavior can subclass it, extend it and add new features. This ensures that superclass behavior remains intact and the clients to the superclass do not have to make code changes. However, inheritance introduces tight coupling between the superclass and subclass implementations. Suppose there is a defect in the superclass or there is an upgrade in the superclass version which introduces code changes, then the subclass also needs to be modified.

Example: Suppose Jane starts a bakery shop and decided to keep it simple and scale as in accordance with her sales.

Java




// Java Program to Illustrate Open Closed Design Principle
// Using Inheritance
 
// Importing input output classes
import java.io.*;
 
// Class 1
// Helper class acting as parent class
class Cake {
 
    // Member variable of Cake class
    private int size;
    private float weight;
 
    // Constructor for cake which sets
    // only size and dimension of cake
    public Cake(int size, float weight)
    {
        // This keyword refers to current object itself
        this.size = size;
        this.weight = weight;
    }
 
    // Method
    // To bake the cake
    public void bake()
    {
        // Display message only
        System.out.println(
            "Baking cake with base as vanilla");
 
        // Print and display the size and weight of the cake
        System.out.println("Size is " + this.size
                           + " inches and weight is "
                           + this.weight + " kg.");
    }
}
 
// Class 2
// Helper class(Child class) of class 1
class PineappleCake extends Cake {
 
    // Member variables
    private int size;
    private float weight;
 
    // Constructor
    // To set the dimension of the pineapple cake
    public PineappleCake(int size, float weight)
    {
        // Super keyword refers to parent class instance
        super(size, weight);
 
        // This keyword refer to current instance
        this.size = size;
        this.weight = weight;
    }
 
    // Method 1
    // To decorate the pineapple cake
    private void decorateCake()
    {
        // Display messages only
        System.out.println("Decorating cake");
        System.out.println("Adding pineapple pieces");
    }
 
    // Method 2
    // To add cream to pineapple cake
    private void addCream()
    {
        // Print statement
        System.out.println("Adding white cream");
    }
 
    // Method 3
    // To bake a pineapple cake
    public void bake()
    {
 
        super.bake();
 
        // Calling the above two methods created
        addCream();
        decorateCake();
 
        // Print the dimension of the pineapple cake
        System.out.println("Pineapple cake - " + this.size
                           + " inches is ready");
    }
}
 
// Class 3
// Helper class(Child class) of class 1
class ChocolateCake extends Cake {
 
    // member variables
    private int size;
    private float weight;
 
    // Constructor
    // Setting the size nd weight of the chocolate cake
    public ChocolateCake(int size, float weight)
    {
 
        super(size, weight);
 
        this.size = size;
        this.weight = weight;
    }
 
    // Method 1
    // To decorate a chocolate cake
    private void decorateCake()
    {
 
        // Display commands only
        System.out.println("Decorating cake");
        System.out.println("Adding chocolate chips");
    }
 
    // Method 2
    // To add cream to chocolate cake
    private void addCream()
    {
 
        // Print statement
        System.out.println("Adding chocolate cream");
    }
 
    // Method 3
    // to bake a chocolate cake
    public void bake()
    {
 
        super.bake();
 
        // Calling the above two methods created
        addCream();
        decorateCake();
 
        // Print and display the dimension of chocolate cake
        System.out.println("Chocolate cake - " + this.size
                           + " inches is ready");
    }
}
 
// Class 4
// Main class
class GFG {
 
    // Main driver method
    public static void main(String[] args)
    {
 
        // Creating an instance of pineapple cake
        // int the main() method
 
        // Custom dimension are passed as in arguments
        PineappleCake pineappleCake
            = new PineappleCake(7, 3);
 
        // Calling the bake() method of PineappleCake class
        // to bake the cake
        pineappleCake.bake();
 
        // Similarly, creating an instance of chocolate cake
        // in the main() method
        ChocolateCake chocolateCake
            = new ChocolateCake(5, 2);
 
        // Calling the bake() method of ChocolateCake class
        // to bake the cake
        chocolateCake.bake();
    }
}


Output

Baking cake with base as vanilla
Size is 7 inches and weight is 3.0 kg.
Adding white cream
Decorating cake
Adding pineapple pieces
Pineapple cake - 7 inches is ready
Baking cake with base as vanilla
Size is 5 inches and weight is 2.0 kg.
Adding chocolate cream
Decorating cake
Adding chocolate chips
Chocolate cake - 5 inches is ready

Output explanation: ‘Cake’ class is the base class. The base for every cake has a vanilla flavor. We have two specializations i.e Pineapple and Chocolate flavor cake.  These classes use the Cake’s bake() method to make a vanilla base cake and then add cream and decorate the cake based on the flavor.

Note: After few days, Jane’s bakery shop sales are high. So she decides to bake cakes with different bases to offer more varieties to her customers. With these new requirements, we need to change the ‘Cake’ class constructor, so do the changes in the above code will be reflected. The code changes are as below. However, we need to modify the subclasses to accept a 3rd parameter – flavor for the code to work.

Example: 

Java




// Java Program to Illustrate Open Closed Design Principle
// Using Inheritance
// Alonside adding Parameter - Flavor to cakes
 
// Importing input output classes
import java.io.*;
 
// Class 1
// Helper class
// Acting as parent class
class Cake {
 
    // Member variables
    private int size;
    private float weight;
 
    // This is new declared variable to
    // hold the flavour for the cake
    private String flavor;
 
    // Constructor
    // To set the dimensions for the cake
    public Cake(int size, float weight, String flavor)
    {
 
        this.size = size;
        this.weight = weight;
 
        // Here this keyword sets the
        // current instance flavour to the cake
        this.flavor = flavor;
    }
 
    // Method
    // To bake the cake
    public void bake()
    {
 
        // Printing the flavour of the desired cake
        System.out.println("Baking cake with base as "
                           + this.flavor);
 
        // Printing the size and weight of the same cake
        System.out.println("Size is " + this.size
                           + " inches and weight is "
                           + this.weight + " kg.");
    }
}
 
// Class 2
// Helper class
class PineappleCake extends Cake {
 
    // Member variables for the pineapple cake
    private int size;
    private float weight;
    private String flavor;
 
    // Constructor
    // Updated as per requirements as sales rise flavour is
    // added, so do setting  the flavour
    public PineappleCake(int size, float weight,
                         String flavor)
    {
 
        // Super keyword refers to parent class
        super(size, weight, flavor);
 
        // This keyword refers to current instance
        this.size = size;
        this.weight = weight;
        this.flavor = flavor;
    }
 
    // Method 1
    // To decorate the cake
    private void decorateCake()
    {
 
        // Display commands only
        System.out.println("Decorating cake");
        System.out.println("Adding pineapple pieces");
    }
 
    // Method 2
    // To add cream to the flavored pineapple cake
    private void addCream()
    {
 
        System.out.println("Adding white cream");
    }
    // Method 3
    // To bake a flavored pineapple cake
    public void bake()
    {
 
        // Super keyword calls the Cake class bake() method
        super.bake();
 
        // Calling the above two methods
        addCream();
        decorateCake();
 
        // Printing the dimensions of
        // flavoured pineapple cake
        System.out.println("Pineapple cake - " + this.size
                           + " inches is ready");
    }
}
 
// Similarly setting the same for
// the 'flavoured' chocolate cake
 
// Class 3
// Helper class (Base class of parent class)
class ChocolateCake extends Cake {
 
    private int size;
    private float weight;
    private String flavor;
 
    // Updated constructor
    public ChocolateCake(int size, float weight,
                         String flavor)
    {
 
        super(size, weight, flavor);
 
        this.size = size;
        this.weight = weight;
        this.flavor = flavor;
    }
 
    // Method 1
    // To decorate the flavored chocolate cake
    private void decorateCake()
    {
 
        System.out.println("Decorating cake");
        System.out.println("Adding chocolate chips");
    }
 
    // Method 2
    // To add cream to the flavoured chocolate cake
    private void addCream()
    {
 
        System.out.println("Adding chocolate cream");
    }
 
    // Method 3
    // To bake a flavoured chocolate cake
    public void bake()
    {
 
        super.bake();
 
        addCream();
        decorateCake();
 
        System.out.println("Chocolate cake - " + this.size
                           + " inches is ready");
    }
}
 
// Class 4
// Main class
class GFG {
 
    // Main driver method
    public static void main(String[] args)
    {
        // Creating a pineapple cake in the main() method
        // Custom dimensions are passed as in arguments
        PineappleCake pineappleCake
            = new PineappleCake(7, 3, "vanilla");
 
        // Calling the bake() method of the PineappleCake
        // Class to bake the pineapple cake
        pineappleCake.bake();
 
        // Similarly repeating the same with the chocolate
        // cake
 
        // Creating a chocolate cake by creating an object
        // of ChocolateCake class in the main method
        ChocolateCake chocolateCake
            = new ChocolateCake(5, 2, "chocolate");
 
        // Calling the bake() method of the ChocolateCake
        // Class to bake the chocolate cake
        chocolateCake.bake();
    }
}


Output

Baking cake with base as vanilla
Size is 7 inches and weight is 3.0 kg.
Adding white cream
Decorating cake
Adding pineapple pieces
Pineapple cake - 7 inches is ready
Baking cake with base as chocolate
Size is 5 inches and weight is 2.0 kg.
Adding chocolate cream
Decorating cake
Adding chocolate chips
Chocolate cake - 5 inches is ready

Note: Thus there is tight coupling between the base class that is, ‘Cake’ class, and it’s subclasses.  To avoid tight coupling we go with the interface approach. 

Method 2: Using interfaces and abstract methods  

  • We define an interface with a set of methods.
  • The interface defines a contract, and it is closed for modifications. We could have different implementations of the interface as per the business requirements. This ensures there is loose coupling between classes. The implementation classes are independent of each other.

Implementation: We refactor the previous example for better understanding. We have a Cake interface that has different steps for baking a cake. Pineapple and Chocolate Cake class implement this interface defining their own bread base, cream, and decoration.

Example: 

Java




// Java Program to Illustrate Open Closed Design Principle
// Using Interfaces and abstract methods
 
// Importing input output classes
import java.io.*;
 
// Interface
interface Cake {
 
    // Abstract methods of this interface
    public void bake();
    public void addCream();
    public void decorateCake();
}
 
// Class 1
// Helper class implementing above interface
class ChocolateCake implements Cake {
 
    // Member variables of this class
    private String base;
    private int size;
    private float weight;
 
    // Constructor
    // To set the dimension to chocolate cake
    public ChocolateCake(String base, int size,
                         float weight)
    {
 
        this.base = base;
        this.size = size;
        this.weight = weight;
    }
 
    // @Override
    // Method 1
    // To add cream to chocolate cake
    public void addCream()
    {
        System.out.println("Adding chocolate cream");
    }
 
    // @Override
    // Method 3
    // To bake the chocolate cake
    public void bake()
    {
 
        // Printing the base of the cake
        System.out.println("Baking cake with base as "
                           + this.base);
 
        // Calling the methods
        addCream();
        decorateCake();
 
        // Printing the dimension of the chocolate cake
        System.out.println("Chocolate cake with "
                           + this.size
                           + " inches and weight:"
                           + this.weight + " kg is ready");
    }
 
    // @Override
    // Method 2
    // To decorate the chocolate cake
    public void decorateCake()
    {
 
        // Print statement only
        System.out.println(
            "Cake decoration with choco chips");
    }
}
 
// Repeating the same for pineapple cake
// as we did above for chocolate cake
 
// Class 2
// Helper class implementing the 'Cake' interface
class PineappleCake implements Cake {
 
    // Member variable
    private String base;
    private int size;
    private float weight;
 
    // Constructor
    // To set the dimension to pineapple cake
    public PineappleCake(String base, int size,
                         float weight)
    {
 
        this.base = base;
        this.size = size;
        this.weight = weight;
    }
 
    // @Override
    // Method 1
    // To add cream to pineapple cake
    public void addCream()
    {
 
        System.out.println("Adding white  cream");
    }
 
    // @Override
    // Method 2
    // To decorate the pineapple cake
    public void decorateCake()
    {
 
        System.out.println(
            "Cake decoration with pineapple pieces");
    }
}
 
// @Override
// Method 3
// To bake the pineapple cake
public void bake()
{
 
    System.out.println("Baking cake with base as "
                       + this.base);
    addCream();
    decorateCake();
 
    System.out.println("Pineapple cake with " + this.size
                       + " inches and weight:" + this.weight
                       + " kg is ready");
}
 
// Class 3
// Main class
class GFG {
 
    // Main driver method
    public static void main(String[] args)
    {
 
        // Making cakes using interface and abstract methods
        // by creating objects here in main() method
 
        // Custom dimensional to both cakes are passed
        // as in arguments
 
        // 1. Pineapple cake
        Cake pineappleCake
            = new PineappleCake("vanilla", 7, 3);
 
        // Calling the bake() to
        // bake pineapple cake
        pineappleCake.bake();
 
        // 2. Chocolate cake
        Cake chocolateCake
            = new ChocolateCake("chocolate", 5, 2);
 
        // Calling the bake() to
        // bake chocolate cake
        chocolateCake.bake();
    }
}


Output

Baking cake with base as vanilla
Adding white  cream
Cake decoration with pineapple pieces
Pineapple cake with 7 inches and weight:3.0 kg is ready
Baking cake with base as chocolate
Adding chocolate cream
Cake decoration with choco chips
Chocolate cake with 5 inches and weight:2.0 kg is ready

Output explanation: The interface provides an abstraction layer and promotes loose coupling. In the future, Jane can introduce other flavors of cake with different bases, creams, and decorations.

 



Like Article
Suggest improvement
Share your thoughts in the comments

Similar Reads