The English translation of Interface Segregation Principle is “Interface Segregation Principle”, which is abbreviated as ISP. Robert Martin defines it this way: “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.

In fact, the term “interface” can be used in many situations. In daily life, we can use it to refer to the socket interface, etc. In software development, it can be regarded as a set of abstract conventions, and can specifically refer to the API interfaces between systems, and can specifically refer to interfaces in object-oriented programming languages, etc.

The key to understanding the interface isolation principle is to understand the word “interface”. In this principle, we can think of interfaces as three things:

  • A collection of API interfaces
  • A single API interface or function
  • Interface concepts in OOP

Let’s explain this principle in three different ways.

Interface: A set of API interfaces

Here, let’s use an example to explain. User system provides a set of user-related interfaces for other modules to use, such as: registration, login, access to user information, delete users and so on. The specific code is as follows:

public interface UserService {
  boolean registe(String username, String password);
  boolean login(String username, String password);
  UserInfo getByUsername(String username);
  void deleteByUsername(a);
}
Copy the code

For example, our foreground module only needs the interfaces for registering, logging in, and retrieving user information in UserService, and the deletion of user interfaces is restricted to the background module. Deleting a user is a very discreet operation. If you put it in UserService, any module that uses UserService can use this interface. However, in order to improve system security and prevent other modules from deleting users by mistake, we do not want other modules to be able to call the interface of deleting users.

The simplest solution here is to follow the interface isolation principle, where the caller should not be forced to rely on interfaces it doesn’t need, put the removed user interface in a separate class, and then make it available only to the backend module. The code is as follows:

public interface UserService {
  boolean registe(String username, String password);
  boolean login(String username, String password);
  UserInfo getByUsername(String username);
  void deleteByUsername(a);
}

public interface AdminUserService {
  void deleteByUsername(a);
}

// Use the background module
public class AdminUserImpl implements UserService.AdminUserService {
  / /... Omit code...
}
Copy the code

In the example above, we understand interfaces in the interface isolation principle as a set of interfaces, which can be a microservice interface, a library interface, and so on. When designing these interfaces, if part of the interface is only used by some callers, we should isolate the part of the interface and give it to the corresponding callers, rather than making other callers rely on the part of the interface that will not be used.

Interface: A single API interface or function

Now we understand interfaces as a single interface or function. In this way, the interface isolation principle can be interpreted as: the function design should be single, do not put different functional logic in one function.

For example, let’s look at the following example:

public class Statistics {
  private Long max;
  private Long min;
  private Long average;
}

public Statistics count(List<Long> dataSet) {
  Statistics statistics = new Statistics();
  / /... All kinds of computational logic...
  return statistics;
}
Copy the code

The count() function above, in a sense, is not simple enough, and contains many different statistical functions, such as calculating maximum, minimum, and average values. Following the principle of interface isolation, the count() function should be divided into smaller functions, each responsible for an independent statistical function. Such as the following code:

public Long max(List<Long> dataSet) {}public Long min(List<Long> dataSet) {}public Long average(List<Long> dataSet) {}Copy the code

However, if all of the Statistics information is needed in a project, then the count() function is reasonable. If you have requirements in your project that require only some information from Statistics, in this case you need to split the count() function into the more fine-grained functions above.

At this point, we can see that the interface isolation principle is somewhat similar to the single responsibility principle, although there are slight differences. The single responsibility principle is mainly for the design of modules, classes and interfaces. The interface isolation principle focuses more on interface design and provides a criterion for determining whether an interface is monolithic: it is indirectly determined by how callers use the interface. If the caller uses part of the interface or part of the function of the interface, the design of the interface is not responsible enough.

Interface: Concept of an interface in OOP

We can also think of “interface” as an OOP interface concept, such as interface in Java. Here’s an example to explain.

Suppose the project has three service classes A, B, and C with similar functions for other modules to use. The code is as follows:

public class A {
  public void funA(a) {}}public class B {
  public void funB(a) {}}public class C {
  public void funC(a) {}}Copy the code

Now, we need to implement A new requirement that adds A new function funD() to A and B, and another function funE() to B and C.

We adapt the code to meet the new requirements. The code after transformation:

public interface D {
  void funD(a);
}

public interface E {
  void funE(a);
}

public class A implements D {
  public void funA(a) {}@Override
  public void funD(a) {}}public class B implements D.E {

  public void funB(a) {}@Override
  public void funD(a) {}@Override
  public void funE(a) {}}public class C implements E {
  public void funC(a) {}@Override
  public void funE(a) {}}Copy the code

In the example above, we designed the functionality that needed to be added into a very single interface: the D and E interfaces, and the service classes only needed to rely on the interfaces they needed, satisfying the interface isolation principle. It is more flexible and reusable than designing a single, all-in-one interface, and it also avoids unnecessary effort in code implementation (the service class implements interfaces it doesn’t need).