In programming, SOLID (single function, open close principle, Ridgeway substitution, interface isolation, and dependency inversion) is a mnemonic acronym introduced by Robert C. Martin in the early 21st century, referring to the five basic principles of object-oriented programming and object-oriented design.

The O in The “SOLID” Principle refers to The Open/Closed Principle, abbreviated as OCP.

The open closed principle, which is open for extension but closed for modification, means that an entity is allowed to change its behavior without changing its source code. Most of the 23 classical design patterns are designed to solve the problem of code scalability, and the main design principle is the open and closed principle.

How to understand open to extension, closed to modification?

The open closed principle is open for extension, but closed for modification. In detail, extend existing code (add modules, classes, methods, etc.) rather than modify existing code (modify modules, classes, methods, etc.).

You might think it’s a little fuzzy, but let’s look at this example.

public class ChatService {
    public static boolean filtMessage(String msg, String userId, int level) {
        If the level is less than 10, filter chat messages
        if(level < 10) {
            return true;
        }
        
        return false; }}Copy the code

The sample code above implements simple filtering of chat data. At this point, we have new requirements to filter chat information according to different levels of users’ channels. For example, the user level limit of channel A is 10, and the user level limit of channel B is 15. How should we modify it?

public class ChatService {
    // Change 1: Add channel channel parameters and all places where this method is called
    public static boolean filtMessage(String msg, String userId, int level, int channel) {
        
        / / modify 2
        // Channel A limits the user level to 10. If the user level is smaller than 10, chat messages are filtered
        if (channel == 1) {
            if(level < 10) {
                return true; }}// The user level of channel B is 15. If the user level is less than 15, the chat information is filtered
        if (channel == 2) {
            if(level < 15) {
                return true; }}return false; }}Copy the code

There are several problems with the above code modification:

  • The filtMessage method argument is modified, along with all code that calls this method.
  • The filtMessage method logic is changed, and the parameters are changed, as are the corresponding unit tests;

The above way, is based on the modification of the existing code to achieve the requirements, obviously violated the open closed principle. Let’s look at the following ways:

public class Message {
    private String msg;
    private String userId;
    private int level;
    privater int channel;
    
    // getter & setter
}

public interface Filter {
    boolean check(Message msg);
}

// Channel A limits the user level to 10. If the user level is smaller than 10, chat messages are filtered
public class ChannelAFilter implements Filter {
    public boolean check(Message msg) {
        if (msg.getChannel() == 1) {
            if(msg.getLevel() < 10) {
                return true; }}return false; }}// The user level of channel B is 15. If the user level is less than 15, the chat information is filtered
public class ChannelBFilter implements Filter {
    public boolean check(Message msg) {
        if (msg.getChannel() == 2) {
            if(msg.getLevel() < 15) {
                return true; }}return false; }}public class ChatService {
    private List<Filter> filters = new ArrayList<>();

    public static boolean filtMessage(Message msg) {
        for (Filter filter : filters) {
            if(filter.check(msg)) {
                return true; }}return false; }}Copy the code

Based on the above example, two adjustments were made:

  • Encapsulate the parameters of the filtMessage method to Message. With the iteration of functionality, new constraints can be added directly to Message instead of adding parameters directly to the filtMessage method.
  • Split the detection logic of the filtMessage method into different filters. If there is a new restriction rule, we can directly add Filter to meet the requirements.

The refactored code is more flexible and extensible. All we need to do is add unit tests for the new Filter class, and none of the old unit tests will fail or be modified.

How to be open to extension and closed to modification?

In order to expand the code, expand, abstract, encapsulate these subconscious, we should always maintain, just like when playing LOL, we should have a gANK consciousness, not eternal wild.

At the same time, we should also consider that expansion points should be reserved to deal with future requirement changes, so that requirements can be completed at a small cost without changing the overall structure of the code. The new code can be flexibly expanded, which is open to expansion and closed to modification.

Distinguish the variable and constant code, the variable encapsulation, isolation, abstract into an immutable interface, to the underlying system use. When new changes come in, we extend them based on the interface, replacing the old implementation or adding new ones, and the underlying system hardly changes (unless the original design becomes inadequate and needs to be refactored).

Among the many design principles, ideas, and patterns most commonly used to improve code extensibility are: polymorphism, dependency injection, programming based on interfaces rather than implementations, and most design patterns (e.g., decorators, policies, templates, chains of responsibility, state, etc.).

It makes sense to do some extensibility design in advance, after code is written, for certain cases where extensions are likely to occur in the short term, or where changes in requirements have a significant impact on the code structure, or where the implementation cost is not high. However, for requirements that are uncertain to be supported in the future, or extension points that are complex to implement, we can wait until the requirements are driven and then refactor the code to support the extension requirements.

And the open close principle isn’t perfect. In some cases, the extensibility of code conflicts with readability. The code was refactored to better support extensibility, and the refactored code was much more complex and difficult to understand than the previous code. Many times, there is a trade-off between extensibility and readability. In some cases, extensibility is important, and readability can be compromised. In other scenarios, readability is more important, and extensibility is compromised.