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

Interface segregation principle (ISP)

ISP was originally used and developed by Robert C. Martin while consulting for Xerox. Xerox created a new printer system that can perform a variety of tasks, such as binding and faxing. The system’s software was created from scratch. As software grows, changes become increasingly difficult, so much so that even the smallest changes can require one-hour redeployment cycles, making development nearly impossible.

The design problem is that almost all tasks use a single Job class. The Job class is called whenever a print or binding Job needs to be performed. This results in a “fat” class that contains multiple methods specific to a variety of different clients. Because of this design, the bookbinder will know all the methods of the print job, even if they are useless.

The solution suggested by Martin leverages today’s so-called interface isolation principle. In Xerox software, an interface layer is added between Job class and its client by dependency inversion principle. Instead of having a big Job class, we create the Staple Job interface or Print Job interface, which are used by the Staple or Print classes, respectively, to call the methods of the Job class. Therefore, an interface is created for each Job type, all of which are implemented by the Job class.

How do you understand the interface isolation principle

Robert Martin defines it this way in the SOLID principle: “Clients should not be forced to depend upon interfaces that they do not use.” A client should not be forced to rely on interfaces it does not need. The “client” can be understood as the caller or consumer of the interface.

To understand the interface isolation principle, the emphasis is on how to understand the “interface”, we can start from the following aspects:

  • A collection of API interfaces
  • A single API interface or function

A collection of API interfaces that act as “interfaces.

Now we have a set of account-related interfaces for the background to query the account system call, including: get account ID, get account name, get account amount. Take a look at the following examples:

public interface AccountService {
    String getAccountId(a);
    String getAccountName(a);
    long getMoney(a);
}

public class AccountServiceImpl implements AccountService {
    public String getAccountId(a) {... }public String getAccountName(a) {... }public long getMoney(a) {...}
}
Copy the code

In this case, the background account management system needs to increase or decrease the amount of the account, and the account system is expected to provide corresponding interfaces.

Now, what do we need to do? You might add addMoney(Long) and deductMoney(Long) methods directly to AccountService. Function is to achieve the solution, but here will not be careful small partners dug pits.

Why is there a pit here? The increase or decrease of the amount of the account is a very important operation. Our expectation is that the increase or decrease of the amount can only be provided to the background management account system, which cannot be called by other systems. Otherwise, one day, a small partner’s hand shakes, the pit will be big. From the perspective of service architecture, interface authentication can be used to limit interface invocation and prevent misoperation of account amount.

Better implementation, according to the interface isolation principle ISP, the client should not be forced to rely on the interface it does not need, need to increase or decrease the account amount of the interface in a separate LimitedAccountService interface class, the interface is limited to the backend management account system calls. Other systems provide only the AccountService interface, which is implemented as follows:

public interface LimitedAccountService {
    void addMoney(long money);
    void deductMoney(long money);
}
public class LimitedAccountServiceImpl implements LimitedAccountService {
    public void addMoney(long money) {... }public void deductMoney(long money) {...}
}
Copy the code

When designing a microservice or library interface, if part of the interface is only used by some callers, we need to isolate this part of the interface and give it to the corresponding callers, rather than forcing other callers to rely on the part of the interface that will not be used.

A single API interface or function as an “interface.

Another way to think about “interface” is to think about a single API interface or function (referred to as “function” for later description). For the function, for the understanding of the interface is: function design should be a single function, do not implement multiple different function logic in a function. Next, let’s look at an example:

// Player information statistics BO class
public class UserStatistics {
    private int maxLevel;
    private int minLevel;
    private int averageScore;
    // getter and setter
}

public UserStatistics calculate(List<User> users) {
    UserStatistics userStatistics = new UserStatistics();
    // Player stats...
    return userStatistics;
}
Copy the code

The method calculate includes the calculation of highest grade, minimum grade and average score. Of course, there are subjective factors to determine whether a function has a single responsibility, and there are also situational factors. For example, in some cases, we only need maxLevel, minLevel, and averageScore in other cases. If we called the calculate function every time, we would calculate all the information, which would cause a lot of wasted CPU resources. If we had a large amount of data, it would cause a lot of performance pressure on the system.

In this scenario, the calculate function does not make sense. Let’s implement it differently:

public int calculateMaxLevel(List<User> users) {... }public int calculateMinLevel(List<User> users) {... }public int calculateAverageScore(List<User> users) {... }Copy the code

In the second implementation, we solved the problem mentioned above by splitting the calculate function into multiple interface implementations and calling it on demand.

At this point, one might wonder how the interface isolation principle and single responsibility are so similar. In fact, the two principles focus on different points. A single responsibility focuses on the design of modules, classes and interfaces. The interface isolation principle focuses on the design of the interface, and the caller only uses part of the interface or part of the function of the interface. We can consider the design of the interface not single responsibility.