Do you REALLY know what SOLID means? (#4: Interface Segregation Principle)

Interface Segregation

The philosophy of Interface Segregation principle is – larger interfaces should be split into smaller ones.
By doing so, we can ensure that implementing classes only need to be concerned about the methods that are of interest to them.
The principle states that many client-specific interfaces are better than one general-purpose Interface. Clients should not be forced to implement a function they do no need. if we have an inherited method or property which does a no-operation or throws an exception when we are forced to implement it; then something has gone wrong and will go wrong when we try to write code that works for all instances of the base class.

in SOLID there are five basic principles which help to create good (or SOLID) software architecture. SOLID is an acronym where:-

Let’s look at an example to understand why this Principle is helpful. We’ll create some code for a burger place where a customer can order a burger, fries or a combo of both:

Interface OrderService {
    void orderBurger(int quantity);
    void orderFries(int fries);
    void orderCombo(int quantity, int fries);
}

Since a customer can order fries, or a burger, or both, we decided to put all order methods in a single Interface. Now, to implement a burger-only order, we are forced to throw an exception in the orderFries() method:

class BurgerOrderService implements OrderService {
    @Override
    public void orderBurger(int quantity) {
        System.out.println("Received order of "+quantity+" burgers");
    }

    @Override
    public void orderFries(int fries) {
        throw new UnsupportedOperationException("No fries in burger only order");
    }

    @Override
    public void orderCombo(int quantity, int fries) {
        throw new UnsupportedOperationException("No combo in burger only order");
    }
}

Similarly, for a fries-only order, we’d also need to throw an exception in orderBurger() method. And this is not the only downside of this design. The BurgerOrderService and FriesOrderService classes will also have unwanted side effects whenever we make changes to our abstraction. Let’s say we decided to accept an order of fries in units such as pounds or grams. In that case, we most likely have to add a unit parameter in orderFries(). This change will also affect BurgerOrderService even though it’s not implementing this method!

So, Should Interfaces Always Have a Single Method?

Applying the ISP to the extreme will result in single-method interfaces, also known as role interfaces. This solution will solve the problem of ISP violation. But, it can and most certainly will result in a violation of cohesion in interfaces, resulting in the scattered code base that is hard to maintain. 

But keep in mind; One single Interface in isolation neither violates nor conforms to the Interface Segregation Principle. The principle is ‘followed or not’ by classes that depend on the Interface; not by the Interface. One class could depend on the Interface and use every method, not violating the principle. Another could depend on the Interface and use one method, violating the principle. Does this mean that the Interface both violates and does not violate the principle? No. It means that one class depending on it does and the other does not. An Interface in isolation it doesn’t illustrate the ISP.

It’s also worth considering the reason behind the principle, which is that if multiple classes depend on different methods of an Interface, one “client” might exert backwards pressure to change the Interface, which means modifying an Interface and possibly implementations which could affect unrelated code. It’s a form of coupling.

The Interface segregation principle is not about disallowing access to what we don’t need. It’s about NOT INSISTING on access to what we don’t need.

interface segregation

Code Smells for ISP Violations

Whether working solo or in larger teams, it helps to identify problems in code early. So, let’s discuss some code smells which could indicate a violation of the ISP.

A Bulky Interface

In bulky interfaces, there are too many operations, but for most objects, these operations are not used. The ISP tells us that we should need most or all methods of an Interface, and in a bulky Interface, we most commonly only need a few of them in each case. Also, when testing a bulky Interface, we have to identify which dependencies to mock and potentially have a giant test setup.

Unused Dependencies

Another indication of an ISP violation is when we have to pass null or equivalent value into a method.

Methods Throwing Exceptions

If we encounter a lot of UnsupportedOperationException, a NotImplementedException, or similar exceptions, it smells like a design problem related to the ISP.
It might be a good time to refactor these classes.

Summary

the purpose of ISP (Interface Segregation Principle) is to keep interfaces small and focused: all Interface members should have very high cohesion. Interface segregation and SRP (Single responsibility principle) have the same goal: ensuring small, highly cohesive software components. They complement each other. Interface segregation ensures that interfaces are small, focused and highly cohesive. Following the Single responsibility principle ensures that classes are small, focused and highly cohesive.

Interface Segregation, in general, should not be based on client requirements. We should change the whole approach to achieve it. We need to modularize the Interface by grouping the features into coherent groups. That is; grouping is based on the coherence of features themselves, not the client requirements. In that case, we will have a set of interfaces, I1, I2 etc. Client C1 may use I2 alone. Client C2 may use I1 and I5 etc. Note that, if a client uses more than one Interface; is not a problem. If we have decomposed the Interface into coherent modules, that is where the heart of the matter is.

Again, ISP is not client-based. It is about decomposing Interface into smaller modules. If it this is done properly, it will also ensure that clients are exposed to as few features as they need.

With this approach, our clients can increase to any number but our number of module is unaffected. Each client will use one or some combination of the interfaces based on their need. Will there be cases that a client, C, need to include say I1 and I3, but not use all the features of these interfaces? Yes, that is not a problem. It just uses the least number of interfaces.