SoftwareYoga

Articles on Coding and Architecture

SOLID: Open Closed Principle

This is the second post in the series on SOLID Software Principles. We previously covered the Single Responsibility Principle. In this post, we cover the second of the SOLID Principles – The O in SOLID - The Open Closed Principle.

Software systems need to be maintainable and easily extendable in the long run. Good architects and programmers always have in mind that code needs to be written in such a way that it is easy to add new functionality in the future.

We should never ignore the obvious fact that new requirements will surely arise in the future. And when new functionality is added to existing code, the change could potentially break the code or introduce defects.

In all fairness, unit tests and integration tests exist just to catch these type of issues. However, wouldn’t it be great if we develop the code in such a way that we never modify existing code, but only add new code whenever new functionality is needed? That way, the unit and integration tests will be used to verify whether your code delivers the required functionality rather than to check if the code has broken because of the changes you made!

Open Closed Principle

The Open Closed principle, first conceptualized by Bertrand Meyer is a Object oriented software design principle. It goes as below:

SOFTWARE ENTITIES (CLASSES, MODULES, FUNCTIONS, ETC.) SHOULD BE OPEN FOR EXTENSION, BUT CLOSED FOR MODIFICATION.

The essence of the Open Closed principle is that code once written should not be modified again in the future. i.e. it should be closed for modification.

If new functionality needs to be implemented, then it should be done by extending the code and not by modifying existing code i.e. open for extension. This way, we can always be sure that existing code is still functional as before since it was never modified.

Implementing Open Closed code

While it may sound easy in theory, implementing this in real world projects in tough, dare I say the toughest to implement among all of the SOLID principles.

Surprisingly enough, this is achieved using the most fundamental aspects of Object oriented programming - Inheritance and Abstraction.

First, let us take an example of a piece of code not confirming to the Open Closed principle.

class Circle {

    private int radius;

    public Circle(int radius) {
        this.radius = radius;
    }

    public int getRadius() {
        return radius;
    }
}

class Square {

    private int side;

    public Square(int side) {
        this.side = side;
    }

    public int getSide() {
        return side;
    }
}

class DrawShapes {

    public void draw(Object[] shapes) {

        for (Object shape : shapes) {
            if (shape instanceof Circle) {
                Circle c = (Circle) shape;
                System.out.println("Drawing Circle with radius " + c.getRadius());
            } else if (shape instanceof Square) {
                Square s = (Square) shape;
                System.out.println("Drawing Square with side " + s.getSide());
            }
        }
    }
}

In the above example, the class DrawShapes is not adhering to the Open Closed principle. The reason being that any introduction of a new type of shape will need change in the class as well as tge draw() method – an additional if/else statement to handle the new shape.

Of course, this is a simplified example. In the real world code, the complexity and the effort needed to introduce and test the code will be several orders higher.

Now, lets have a look at the same functionality, but this time adhering to the principle.

abstract class Shape {

    abstract void draw();
}

class Circle extends Shape {

    private int radius;

    public Circle(int radius) {
        this.radius = radius;
    }

    public int getRadius() {
        return radius;
    }

    @Override
    void draw() {
        System.out.println("Drawing Circle with radius " + this.getRadius());
    }
}

class Square extends Shape {

    private int side;

    public Square(int side) {
        this.side = side;
    }

    public int getSide() {
        return side;
    }

    @Override
    void draw() {
        System.out.println("Drawing Square with side " + this.getSide());
    }
}

class DrawShapes {

    public void draw(Shape[] shapes) {

        for (Shape shape : shapes) {
            shape.draw();
        }
    }
}

In the above code, the draw() method has been extracted out into a new abstract class. Each of the individual types of shape classes then extend the abstract class and provide their own implementation of the draw method. The DrawShapes class only uses the abstractions. It remains untouched when adding a new shape type because it is not concerned with the implementation details of each type of shape.

The Open Closed Principle is dependent on basics of Object Oriented Design - Inheritance, Abstraction and Encapsulation.

Only a new class implementation for the new shape is needed in order to be able to draw it. No existing code is modified in the process.

Note

It is worth noting that no code in the real world projects can be 100% closed, all we can try to do is to make the code as closed as possible. For example, if we need a functionality in the above code to draw all the circles first and then all the Squares, that will need a change to the DrawShapes class and the draw() method.

Important considerations

The yet another fundamental aspect of object oriented programming that helps make the code adhere to the Open Closed principle is Encapsulation.

  1. Private member variables When a member variable in a class is public or protected, it can be modified from other classes and sub-classes. Hence, it should be the intention of the developer to make the member variables private.
  2. Final member variables Building on Point 1, there might be member variables that never should be modified. These should be declared as final so that they are closed for modification.
  3. No global variables No global variables should be left available for modification. All global variables should be declared as static final.

Note that in the original version of the principle, the recommendation was that global variables should never be used because there was no way to restrict other modules overwriting the variable. But this was based on C/C++ code and not applicable to Java.

Conclusion

We already covered the Single Responsibility Principle in the previous post in this series. In general, a class or a method that strictly follows the Single responsibility principle will be almost close to following the Open Closed principle as well. In most cases, it will only require a minor effort from the developer.

Together, both the principle when applied while writing the code will result in high quality, maintainable and readable code with little or no issues.