Interface Segregation Principle – Resolving Contradicting Definitions

design-principlessolid

When reading articles on ISP, there seem to be two contradicting definitions of ISP:

According to first definition ( see 1, 2, 3), ISP states that classes implementing the interface shouldn't be forced to implement functionalities which they don't need. Thus, fat interface IFat

interface IFat
{
     void A();
     void B();
     void C();
     void D();
}

class MyClass: IFat
{ ... }

should be split into smaller interfaces ISmall_1 and ISmall_2

interface ISmall_1
{
     void A();
     void B();
}

interface ISmall_2
{
     void C();
     void D();
}

class MyClass:ISmall_2
{ ... }

since this way my MyClass is able to implement only the methods it needs ( D() and C() ), without being forced to also provide dummy implementations for A() and B():

But according to the second definition ( see 1 , 2, answer by Nazar Merza), ISP states that MyClient calling methods on MyService shouldn't be aware of methods on MyService that it doesn't need. In other words, if MyClient only needs the functionality of C() and D(), then instead of

class MyService 
{
    public void A();
    public void B();
    public void C();
    public void D();
}

/*client code*/      
MyService service = ...;
service.C(); 
service.D();

we should segregate MyService's methods into client-specific interfaces:

public interface ISmall_1
{
     void A();
     void B();
}

public interface ISmall_2
{
     void C();
     void D();
}

class MyService:ISmall_1, ISmall_2 
{ ... }

/*client code*/
ISmall_2 service = ...;
service.C(); 
service.D();

Thus with the former definition, the goal of ISP is to "make the life of classes implementing IFat interface easier", while with the latter the goal of ISP is to "make the life of clients calling methods of MyService easier".

Which of the two different definitions of ISP is actually correct?

@MARJAN VENEMA

1.

So when you are going to split IFat into smaller interface, which
methods end up in which ISmallinterface should be decided based on how
cohesive the members are.

While it makes sense to put cohesive methods within the same interface, I thought with ISP pattern the needs of the client take precedence over the "cohesiveness" of an interface. In other words, I thought with ISP we should lump within the same interface those methods needed by particular clients, even if that means leaving out of that interface those methods that should, for the sake of cohesiveness, also be put inside that same interface?

Thus, if there were lots of clients that will only ever needed to call CutGreens, but not also GrillMeat, then to adhere to ISP pattern we should only put CutGreens inside ICook, but not also GrillMeat, even though the two methods are highly cohesive?!

2.

I think that your confusion stems from the a hidden assumption in the
first definition: that the implementing classes are already following
the single responsibility principle.

By "implementing classes not following SRP" are you referring to those classes that implement IFat or to classes that implement ISmall_1 / ISmall_2? I assume you're referring to classes that implement IFat? If so, why do you assume they don't already follow SRP?

thanks

Best Answer

Both are correct

The way I read it, the purpose of ISP (Interface Segregation Principle) is to keep interfaces small and focused: all interface members should have very high cohesion. Both definitions are intended to avoid "jack-of-all-trades-master-of-none" interfaces.

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.

The first definition you mention focuses on implementers, the second on clients. Which, contrary to @user61852, I take to be the users/callers of the interface, not the implementers.

I think that your confusion stems from the a hidden assumption in the first definition: that the implementing classes are already following the single responsibility principle.

To me the second definition, with the clients as the callers of the interface, is a better way of getting to the intended goal.

Segregating

In your question you state:

since this way my MyClass is able to implement only the methods it needs ( D() and C() ), without being forced to also provide dummy implementations for A(), B() and C():

But that is turning the world upside down.

  • A class implementing an interface does not dictate what it needs in the interface it is implementing.
  • The interfaces dictate what methods an implementing class should provide.
  • The callers of an interface really are the ones that dictate what functionality they need the interface to provide for them and thus what an implementer should provide.

So when you are going to split IFat into smaller interface, which methods end up in which ISmall interface should be decided based on how cohesive the members are.

Consider this interface:

interface IEverythingButTheKitchenSink
{
     void DoDishes();
     void CleanSink();
     void CutGreens();
     void GrillMeat();
}

Which methods would you put in ICook and why? Would you put CleanSink together with GrillMeat just because you happen to have a class that does just that and a couple of other things but nothing like any of the other methods? Or would you split it into two more cohesive interfaces, such as:

interface IClean
{
     void DoDishes();
     void CleanSink();
}

interface ICook
{
     void CutGreens();
     void GrillMeat();
}

Interface declaration note

An interface definition should preferably be on its own in a separate unit, but if it absolutely needs to live with either caller or implementer, it should really be with the caller. Otherwise the caller gets an immediate dependency on the implementer which is defeating the purpose of interfaces altogether. See also: Declaring interface in the same file as the base class, is it a good practice? on Programmers and Why should we place interfaces with classes that use them rather than those that implement them? on StackOverflow.

Related Topic