Pergunta

In Chapter 10 of Clean Architecture, Martin gives an example for the Interface Segregation Principle. I have some trouble understanding that example and his explanations.

In this example we have three separate Users (Classes) that use a Class called OPS. OPS has three methods, op1, op2, and op3. Each of these is only used by one user (op1 only by User1 and so on).

Martin now tells us that any change in OPS would result in a recompilation for the other classes since they all depend on OPS, even if the change was performed in a method that is of no interest to them. (So a change in op2 would require a recompilation of User1.)

He argues that thus there should be three separate interfaces, one for each method. The OPS class then implements all of them. The users only use the interface they use. So you have User1 implementing only Interface1 and so on.

According to Martin, this would stop the otherwise necessary redeployment of, say, User1 if the implementation of ops2 in OPS was changed (since User1 does not use the interface that describes op2).

I had my doubts and did some testing. (Martin explicitly used Java for his example, so I did as well.) Even without any interfaces any change in OPS does not cause any user to be recompiled.

And even if it did (which I thought it would), using three interfaces and then having the same class implement all three of them makes no sense to me either. Wouldn't any change in that class require all of the users to be recompiled, interface or no? Is the compiler smart enough to separate where I did my changes and then only recompile those users that rely on the interface describing the method I changed? I kind of doubt that.

The only way how this principle makes sense to me is if we were to split the OPS class into three different classes, interfaces or no. That I could understand, but that's explicitly not the answer Martin gives.

Any help would be greatly appreciated.

Foi útil?

Solução

In that particular example the use of Java as compared to C++ does indeed hide the benefit of interfaces. Interfaces (or more generally, late binding) are useful to break direct dependencies.

Direct dependency    Indirect dependency
=================    ===================

+---------+              +-----------+
| Service |              | Interface |
+---------+              +-----------+
     ^                     ^        ^
     |                     |        |
 +--------+          +---------+ +--------+
 | Client |          | Service | | Client |
 +--------+          +---------+ +--------+

However, Java already uses some amount of late-ish binding: the .class files that the Java compiler produces are not linked in a meaningful way. Instead, the kind of linking in the sense of C++ happens later during runtime when the JVM loads the .class file. Therefore, the focus on Java's compilation model is misleading here. Furthermore, a focus on avoiding recompilation would mean that the ISP would be irrelevant for interpreted language implementations, which is nonsensical.

To be clear: breaking direct dependencies through polymorphism (and applying the ISP more carefully) does have significant effect on compile times in ahead-of-time compiled systems (such as nearly all C++ implementations). This is also related to C++ specific idioms such as pImpl.

The much more universal ISP benefit is that by avoiding unneeded dependencies, the system becomes

  • easier to understand;
  • easier to test;
  • easier to change.

One kind of unneeded dependency is the dependency of a client on a specific service implementation, which can be avoided via polymorphism/interfaces (compare the Dependency Inversion Principle). Another unneeded dependency is the dependency of a consumer on extra methods in an interface. Keeping interfaces small and segregated minimizes these extra dependencies.

The main takeaway from the ISP for me is that

  1. Interfaces should be defined by how an object is used, not by how the object is implemented. It is preferable to define an interface at the point of its usage, and to then implement this interface (possibly by implementing an adapter to an existing class).
  2. Small interfaces are basically the Single Responsibility Principle applied to interfaces.

Outras dicas

Note that even if you don't "implement" an interface, the public members of your class form an implicit interface.

I had my doubts and did some testing. (Martin explicitly used Java for his example, so I did as well.) Even without any interfaces any change in OPS does not cause any user to be recompiled.

This is true only if you did not change the public interface of OPS, i.e. you only changed implementation details. If you added, removed or changed the signature of any public methods, it could potentially break existing consumers and any good build tool would recompile your user classes.

Wouldn't any change in that class require all of the users to be recompiled, interface or no?

If your clients are only coupled to an interface, they don't need to be recompiled as long as the interface doesn't change. If OPS changes in a way that it no longer correctly implements an interface, it causes a compile error in OPS, not in the user classes.

The only way how this principle makes sense to me is if we were to split the OPS class into three different classes, interfaces or no.

Think of a class as "how something is implemented" and an interface as "what I can do with it". It might make sense for a single class to implement three interfaces if the implementation happens to provide the services of all three interfaces. No point in splitting it out into three classes in that case.

Licenciado em: CC-BY-SA com atribuição
scroll top