Open Closed Design Principle in Java
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:
- Using inheritance
- 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 sub–class 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(); } } |
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(); } } |
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(); } } |
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.
Contact Us