SOLID Open/closed principle

The open/closed principle (OCP) says that every class, module, method, etc. should be open for an extension but must be closed for modification.

This means that new functionality should be added by extending existing classes rather than modifying them.

This principle is important because it aids in the maintenance and extensibility of code.

It can be difficult to track and manage changes when existing classes are modified to add new functionality, and it can also result in the introduction of new bugs.

With this principle, new functionality can be added to existing classes without modifying existing code, making code more maintainable and less prone to bugs.
What we want is to prevent our code from being unintentionally modified causing errors or unexpected functionality.

This definition “open for an extension but closed for modification” can be somewhat confusing.
Let’s look at an example to apply it.

Poorly implemented code where we do NOT use this open/closed principle

Note this class with the basic functionality for a vehicle.


class Car {

    void accelerate() {
        // accelerate car
    }

    void stop() {
        // stop car
    }

}

Now if we would also like to add functionality for a race vehicle, we might be tempted to do it in the same class.

A race vehicle needs this extra functionality injectExtraGas


class Car {

    void accelerate(boolean isCarRace) {
        if (isCarRace) {
            injectExtraGas();
        }
        // accelerate car
    }

    void stop() {
        // stop car
    }

    private void injectExtraGas() {
        // only for Race Car 
    }

}


This class can be used to accelerate a common Car and to accelerate a race car, but it cannot be used for another type of card like an electric car.

To add support for other cars, we would need to modify the accelerate() method to handle each type of car, creating a very confuse method.

Running the code is more noticeable that this code does not look good. What if we need several types of cars?

public class Demo {

    public static void main(String[] args) {
        
        Car carCoupe = new Car();
        carCoupe.accelerate(false);
        carCoupe.stop();

        Car raceCar = new Car();
        raceCar.accelerate(true);
        raceCar.stop();
        
    }

}

How to implement the open/closed principle

But instead of doing this, to comply with the open/closed principle (OCP), we can extend our class by creating another class type derived from the principal.

Now we create our class RaceCar which is going to extend from ICar and add the new functionality.

The following code example shows a class that implements the OCP.

This code uses an interface to define the ICar class. This interface has two methods, accelerate() and stop(), which all concrete car classes must implement.

interface ICar {
    void accelerate();

    void stop();
}

Car implements ICar

class Car implements ICar {

    public void accelerate() {
        System.out.println("accelerate car");
    }

    public void stop() {
        System.out.println("stop car");
    }

}

RaceCar implements ICar but also adds the particular method it needs injectExtraGas without affecting other classes.


public class RaceCar implements ICar {

    public void accelerate() {
        System.out.println("accelerate race car");
        injectExtraGas();
    }

    public void stop() {
        System.out.println("stop race car");
    }

    private void injectExtraGas() {
        System.out.println("inject extra gas race car");
    }

}

How you can notice, OCP is the principle of polymorphism.

Running our fixed code, looks better:

public class Demo {

    public static void main(String[] args) {

        ICar carCoupe = new Car();
        carCoupe.accelerate();
        carCoupe.stop();

        ICar raceCar = new RaceCar();
        raceCar.accelerate();
        raceCar.stop();
        
    }

}

Conclusion

Following “OCP” we achieve these advantages

  • Maintainability: easier to understand and modify, making it more maintainable.
  • Extensibility: new functionality can be added without modifying existing code, making it more extensible.
  • Reusability: more reusable because it can be used in multiple contexts without being modified.

Applying these principles makes your code reusable, maintainable, and scalable. Also, helps to make it easier to test.

You can review these examples at GitHub

Hi! If you find my posts helpful, please support me by inviting me for a coffee :)

See also