Do you REALLY know what SOLID means? (#4: Exclusive Truth about 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:-

But What Does it Stands For?

The Interface Segregation principle is one of the SOLID principles of object-oriented software design. It states that clients should not be forced to depend on interfaces they do not use. In other words, an interface should be designed in a way that it only exposes the methods and properties that a client needs, rather than a larger set of methods and properties that the client may not use. This helps to reduce the complexity and dependencies of the client, and makes it easier to maintain and modify the interface.

Here is an example of how the Interface Segregation principle can be applied in practice:

Imagine that you are designing an interface for a Shape class, and you want to allow clients to calculate the area and perimeter of the shape. You could create an interface like this:

interface IShape {
    double CalculateArea();
    double CalculatePerimeter();
}

This interface is simple and easy to understand, and it exposes only the methods that a client needs to calculate the area and perimeter of a shape. However, suppose that you later decide to add a method to calculate the volume of a 3D shape. This would require all clients that implement the IShape interface to implement the CalculateVolume() method, even if they are not interested in calculating the volume of a shape. This would violate the Interface Segregation principle, because clients are being forced to depend on an interface they do not use.

What to do?

To avoid this problem, you could create a separate interface for 3D shapes that exposes the CalculateVolume() method:

interface IShape3D { double CalculateVolume(); }

Now, clients that are interested in calculating the volume of a 3D shape can implement the IShape3D interface, while clients that are only interested in calculating the area and perimeter of a shape can continue to implement the IShape interface. This helps to reduce the complexity and dependencies of the clients, and makes it easier to maintain and modify the interfaces.

Interface Segregation Principle
Another Example:

Let’s look at another 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

Code smells for Interface Segregation Principle (ISP) violations can occur when a class implements an interface that has methods that the class does not use. This can lead to a violation of the ISP because it requires the class to implement methods that are not relevant to its functionality, leading to unnecessary complexity and bloat in the code.

Some common code smells for ISP violations include:

  1. Large interfaces: If an interface has a large number of methods, it may be a sign that the interface is not adequately segregated and may be violating the ISP.
  2. Unused methods: If a class implements an interface but does not use all of the methods defined in the interface, it may be a sign that the interface is too broad and is violating the ISP.
  3. Tight coupling: If a class is tightly coupled to an interface with many methods, it may be a sign that the interface is violating the ISP. This can lead to problems with maintainability and flexibility in the code.
  4. Poorly defined responsibilities: If an interface has methods that are unrelated or have conflicting responsibilities, it may be a sign that the interface is violating the ISP. This can lead to confusion and difficulty in understanding the purpose of the interface.

Overall,

Code smells for ISP violations can indicate that an interface is not adequately segregated and may be causing unnecessary complexity and coupling in the code. By addressing these code smells, developers can ensure that their interfaces are in compliance with the ISP and are well-organized and easy to maintain.

There are several signs that a codebase may be violating the Interface Segregation Principle (ISP). One of these is a “bulky interface”, where there are many operations that are not used by most objects. This can make testing difficult and can indicate that the interface is too large and should be broken down into smaller, more specific interfaces. Another sign of an ISP violation is the presence of “unused dependencies”, where null or equivalent values must be passed into a method. Additionally, if there are many exceptions being thrown, such as UnsupportedOperationException or NotImplementedException, this could indicate a design problem related to the ISP and a need to refactor the affected 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.

Leave a Comment