OCP - Open-Closed Principle

Objects or entities should be open for extension, but closed for modification.

The Open-Closed principle (OCP) states that classes should be open for extension but closed for modification. In doing so, the extension allows us to implement new functionality to the module, we stop ourselves from modifying existing code and causing potential new bugs.

Example

To understand this principle, let's look at an example like this:

class UserService {

    public void create(User user) {
        if (user.getPassword().length() >= 8) {
            // do something
        }
    }    

    public void update(User user) {
        if (user.getPassword().length() >= 8) {
            // do something
        }
    }
}

With the above example, if we put the validation inside the logic, we can get the following problem:

  • Adding a new validation we have to modify a lot.

  • Hard to test, we have to test both the logic and the validation of the implementation.

Now, we move the validation to different classes for processing. Then if we want to change another way of validating for the user, just change it in the class validator or create a new instance of the validator.

class UserService {
    private Validator validator;
 
    public UserService(Validator validator) { // Open for extension
        this.validator = validator;
    }
 
    public void save() {
        if (validator.isValid(user)) {
            // do something
        }
    }
    
    public void update() {
        if (validator.isValid(user)) {
            // do something
        }
    }
}
 
interface Validator {
    boolean isValid(User user);
}
 
class UserValidator1 implements Validator {
    @Override
    public boolean isValid(User user) {
        return user.getPassword().length() >= 8;
    }
}

// Open for extension 
class UserValidator2 implements Validator {
    @Override
    public boolean isValid(User user) {
        return isNotBlacklist(user.getUsername()) && isStrongPassword(user.getPassword());
    }
    
    private boolean isNotBlacklist(String username) {
    }
    
    private boolean isStrongPassword(String password) {
    }
}
 
public class OCPExample {
    public static void main(String[] args) {
        UserService userService1 = new UserService(new UserValidator1());
        UserService userService2 = new UserService(new UserValidator2());
    }
}

Doing it this way, we have followed the Single Responsibility Principle (we have moved the additional responsibility to another class, each class only does one thing). Now, we don't have to modify the logic class. If we want to add another way to validate the data, we just need to create a new class and inject it into the constructor.

The Open-Closed Principle can also be achieved in a variety of ways: using inheritance or using compositional design patterns such as Strategy Pattern.

Last updated