9

I have grown into using a principle for designing and consuming interfaces that says basically, "ask for only what you need."

For instance, if I have a bunch of types that can be deleted, I'll make a Deletable interface:

interface Deletable {
   void delete();
}

Then I can write a generic class:

class Deleter<T extends Deletable> {
   void delete(T t) {
      t.delete();
   }
}

Elsewhere in code I will always ask for the smallest possible responsibility to fulfill the needs of the client code. So if I only need to delete a File, I'll still ask for a Deletable, not a File.

Is this principle that is common knowledge and already has an accepted name? Is it controversial? Is it discussed in textbooks?

1
  • 1
    Loose coupling maybe? Or narrow interfaces?
    – tdammers
    Aug 25, 2012 at 20:50

2 Answers 2

16

I do believe that this refers to what Robert Martin calls the Interface Segregation Principle. Interfaces are separated into small and concise ones so that the consumers (clients) will only have to know about the methods that are of interest to them. You can check out more on SOLID.

4

To expand on Vadim's very good answer, I will answer the "is it controversial" question with "no, not really".

In general, interface segregation is a good thing, by reducing the overall number of "reasons to change" of the various objects involved. The core principle is, when an interface with multiple methods must be changed, say to add a parameter to one of the interface methods, then all consumers of the interface must at least be recompiled, even if they did not use the method that changed. "But it's just a recompile!", I hear you say; that may be true, but keep in mind that typically, anything you recompile must be pushed out as part of a software patch, no matter how significant the change to the binary. These rules were originally conceptualized back in the early 90s, when the average desktop workstation was less powerful than the phone in your pocket, 14.4k baud dial-up was blazin', and 3.5" 1.44MB "floppies" were the primary removable media. Even in the current era of 3G/4G, wireless internet users often have data plans with limits, so when releasing an upgrade, the fewer binaries that must be downloaded, the better.

However, like all good ideas, interface segregation can go bad if improperly implemented. First off, there is a possibility that by segregating interfaces while keeping the object that implements those interfaces (fulfilling the dependencies) relatively unchanged, you may end up with a "Hydra", a relative of the "God Object" anti-pattern where the all-knowing, all-powerful nature of the object is hidden from the dependent by the narrow interfaces. You end up with a many-headed monster that is at least as hard to maintain as the God Object would be, plus the overhead of maintaining all its interfaces. There isn't a hard number of interfaces that you shouldn't exceed, but each interface you implement on a single object should be prefaced by answering the question, "Does this interface contribute to the object's single, narrow core purpose, or goes it give the object a new purpose?". If it's the former, implement away; if it's the latter, split the object in two to maintain the Single Responsibility Principle.

Second, an interface per method may not be necessary, despite what SRP may tell you. You may end up with "ravioli code"; so many bite-size chunks that it's difficult to trace through to find out exactly where things actually happen. It's also unnecessary to split an interface with two methods if all current users of that interface need both methods. Even if one of the dependent classes only needs one of the two methods, it's generally acceptable to not split the interface if its methods conceptually have very high cohesion (good examples are "antonymic methods" which are exact opposites of each other).

Interface segregation should be based on the classes that are dependent on the interface:

  • If there is only one class dependent on the interface, don't segregate. If the class doesn't use one or more of the interface methods, and it is the interface's only consumer, odds are you shouldn't have exposed those methods in the first place.

  • If there is more than one class that is dependent on the interface, and all dependents use all methods of the interface, don't segregate; if you must change the interface (to add a method or change a signature), all current consumers will be affected by the change whether you segregate or not (though if you're adding a method that at least one dependent will not need, consider carefully if the change should instead be implemented as a new interface, possibly inheriting from the existing one).

  • If there is more than one class dependent on the interface, and they do not use all the same methods, it's a candidate for segregation. Look at the "coherence" of the interface; do all methods further a single, very specific programming goal? If you can identify more than one core purpose for the interface (and its implementors), consider splitting the interfaces along those lines to create smaller interfaces with fewer "reasons to change".

6
  • It's also worth noting that interface segregation may be fine and dandy if one is using an OOP language/system which can allow code to specify a precise combination of interfaces, but at least in .NET they can cause some severe headaches, since there's no decent way of specifying a collection of "things that implement IFoo and IBar, but might otherwise have nothing in common".
    – supercat
    Feb 24, 2014 at 2:11
  • Generic type parameters can be defined with criteria including the implementation of multiple interfaces, but you're right in that expressions requiring a static type typically cannot support specifying more than one. If there is a need that a static type implement both IFoo and IBar, and you control both of those interfaces, it may be a good idea to implement IBaz : IFoo, IBar and require that instead.
    – KeithS
    Oct 7, 2014 at 17:23
  • If client code might need something that can be used as IFoo and IBar, defining a composite IFooBar may be a good idea, but if interfaces are finely split it's easy to end up requiring dozens of distinct interface types. Consider the following features collections might have: Enumerate, report Count, Read nth element, Write nth element, Insert before nth element, Delete nth element, New item (enlarge collection and return index of new space), and Add. Nine methods: ECRWIDNA. I could probably describe dozens of types which would naturally support many many different combinations.
    – supercat
    Oct 7, 2014 at 17:47
  • Arrays, for example, would support ECRW. An arraylist would support ECRWIDNA. A thread-safe list might support ECRWNA [though A would generally only be useful for pre-populating the list]. A read-only array wrapper might support ECR. A covariant list interface could support ECRD. A non-generic interface could provide type-safe support C or CD. If Swap was an option, some types could support CS but not D (e.g. arrays) while others would support CDS. Trying to define distinct interface types for each necessary combination of abilities would be a nightmare.
    – supercat
    Oct 7, 2014 at 17:55
  • Now imagine one wants the ability to wrap a collection with an object that can do everything the collection can do, but which logs every transaction. How many wrappers would one need? If all collections inherited from a common interface which included properties to identify their abilities, one wrapper would suffice. If all interfaces are distinct, however, one would need dozens.
    – supercat
    Oct 7, 2014 at 17:57

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.