Thursday, September 10, 2009

SOLID part 4: Interface Segregation Principle

This is the fourth post in the SOLID principles series. You might want to subscribe to the feed to be notified of new posts.

Classes should not depend on interfaces that they not use.
The meaning of this phrase is to avoid tying a client class to a big interface if only a subset of this interface is really needed. Many times you see an interface which has lots of methods. This is a bad design choice since probably a class implementing it will infringe Single Responsibility Principle and for many other issues which arises when interfaces grow.

Let's see an example of a violation to Interface Segregation Principle. Since Car examples are becoming popular, we will continue with a vehicle example.
The class Car needs to have reference to its passengers: depending on the particular vehicle, it will transport 4 or more people and it must check during construction that it is not overloaded in weight and number of passengers since it would be not secure to drive over cartain limits. We have a class People ready who acts as a collection of Person objects; so, to load a car with People, the following method is used:
public function load(People $p);
We do not want to tie Car to People, which has in turn other dependencies, so we start with extracting an interface:
public function load(IPeople $p);
Hungarian notation is a smell that something. IPeople has many methods, the same of the People concrete class: getWeight(), remove($i), add(Person $p), and a bunch of other functions which Car will never call. What does Car need to know? This is the question that needs an answer: Car needs only a count() method to avoid being overloaded, and a getWeight() method to calculate acceleration and other physical variables. We put these two methods in an interface which will be implemented by People, deleting the awful IPeople component.
public function load(Passengers $p);
In this design, Car depends on the smallest possible interface, Passengers. An interface with a name that does not derive from its implementations is a sign that we are on a good path. Even if People has to be decoupled from Passengers interface, I strongly suggest to write an adapter implementing Passengers, which will wrap a People instance.
The important part is that Car is subjected only to variations to the method which really use, that is actually the minimum coupling introduced in the application. If such a small interface does not exist, it has to be created via extraction from the previous one.
Please note that the same issues are present in abstract base classes: although they cannot be broken down in tiny pieces since multiple inheritance is forbidden in most languages, providing every possible method in a base class can quickly transform it in a God object, and that's exaclt what we must avoid. Helpers and delegations can be used instead when not every subclass will actually need the parent's method.

Small interfaces respect the SRP, and can be combined in many ways. They can also be implemented by the same object, like many classes does in php with Countable and Iterator. Fortunately these two interfaces are separated and allow an Iterator who does not know its length to work.
The advantage of less polluted interfaces is also in simplicity of implementation: less methods result in less tests and less interaction which can cause bugs; also, the classes derived will be much more cohesive as they take only one responsibility to manage from the chosen interface.

The testing point of view results in a easy win for small interfaces. How do you know what to mock when a 20+ methods interface or base class is passed in the constructor of the system under test? When the interface has two or three methods, there is little choice in what can be called by the SUT and you can produce mocks without having to know the internals of it (which of the 20+ methods will be called at what time). Probably in such a case you will end up using a concrete class instead of a mock, transforming your unit tests in integration ones.
You will be satisfied of keeping methods to a minimum while producing a self-shunting also for testing purposes.

I think you are now at a good point in our journey in the principles of object-oriented development. Interfaces are a great decoupling tool and should be used at their full potential. Stay tuned for the next part, on Dependency Inversion (and not Injection).

The image at the top is a Swiss Army Knife. How would you define an interface for one and someone will implement it? Do you prefer a real toolbox?

4 comments:

Ariel Arjona said...

I'd like to take the time and commend you on an excellent blog. I'm particularly liking this SOLID series.

Unknown said...

This sounds great in theory, but does not work so well in practice. One should segragte interfaces but only so far.

When you segregate too much you end up with proliferation of interfaces. Constructing a reasonable hierarchy for these interfaces is quite painful.

Also, when the code evolves, two previously distinct interfaces may end up being identical. Spotting these interfaces and consolidating them is, again, a non trivial task.

Finally, I don't think this is a universal principle. It is valid only in languages where the typing scheme is both static and nominal. Indeed, current mainstream languages do fall into this category, but the community is gradually shifting to structural typing (Scala) or dynamic typing (Ruby).

Giorgio said...

@Ariel: thanks, I have put a great effort this week in the series. :)
@Itay: of course every rule in programming introduces a trade-off that has to be evaluated before taking it too far. Even the Single Responsibility Principle, if followed till death, results in wrapper classes for numbers and strings.
For the static vs. dynamic languages difference, these principles have to be applied mostly in static languages (Java) or hybrid like php which are dynamic but can define interfaces in the oop sense. Obviously if you are using duck typing there's no interface to segregate, but I would say every single method is an "interface" itself. :)

victorinox knives said...

Your blog is very inspiring, I just love it.

ShareThis