SOLID – The Interface Segregation Principle

We are closing in on the final principle of SOLID. In the last post I wrote about the Liskov Substitution Principle, and now it is time to take a look at The Interface Segregation Principle (ISP).

Definition

The Interface Segregation Principle was coined by Robert C. Martin. It is explained in his book ”Agile Software Development Principles, Patterns, and Practices” from 2002 but has probably been around longer than that. It states that:

Clients should not be forced to depend on methods they do not use

The goal of it is to reduce the side effects and frequency of required changes by splitting the software into multiple, independent, parts.

Code Smells

As the code evolves and more features are added it is common that classes grow and more and more functionality is added to them. This has a tendency to grow into ”fat” interfaces, which can be identified by that their methods can broken up into groups, each group serving a different set of clients. The code will start to smell of:

  • Needless complexity and needless redundancy – when interfaces grow you risk ”interface pollution”, i.e. interfaces that contain methods only needed by one or a few clients. Some implementations may not need all the methods of the interface but are forced to implement degenerated versions of the methods (methods that are empty or throws an exception when called). It may also be tempting to push empty implementations and exception throwing down to the base class, which leads to violations of LSP.
  • Rigidity and Viscosity – A change to an interface, enforced by a single client, will force all other derivatives to be updated. Even though they do note require the functionality.

Fortunately there are some straight forward ways to fix these violations.

How to implement

In order to realize how to implement ISP you need to understand that clients of an object does not need to access it directly, they can instead access it through an abstract base class or a thin interface. In other words, split your fat interfaces into several thinner ones and serve each client the specific interface(s) they need. Let’s see an example, assume you have a class that looks like this:

public class ComplexClass
{
  // Methods called by clients in group A
  public void MethodA1() { ... }
  public void MethodA2() { ... }

  // Methods called by clients in group B
  public void MethodB1() { ... }
  public void MethodB2() { ... }
  public void MethodB3() { ... }

  // Methods called by clients in group C
  public void MethodC() { ... }
}

public class ClientInGroupA
{
  // Only calls methods MethodA1 and A2, but are dependent on the full interface
  private readonly ComplexClass _cc;
  public ClientInGroupA(ComplexClass cc)
  {
     _cc = cc;
  }
  ...
}

public class ClientInGroupB
{
  // Only calls methods MethodB1, B2, and B2, but are dependent on the full interface
  private readonly ComplexClass _cc;
  public ClientInGroupB(ComplexClass cc)
  {
    _cc = cc;
  }
  ...
}

public class ClientInGroupC
{
  // Only calls method MethodC, but are dependent on the full interface
  private readonly ComplexClass _cc;
  public ClientInGroupC(ComplexClass cc)
  {
    _cc = cc;
  }
  ...
}

Instead of handing all the clients the same interface, all public methods of an object of type ComplexClass. You can segregate the interface into three separate new interfaces, one for each group of clients:

public interface IGroupA
{
  void MethodA1();
  void MethodA2();
}

public interface IGroupB
{
  void MethodB1();
  void MethodB2();
  void MethodB3();
}

public interface IGroupC
{
  void MethodC();
}

public class ComplexClass() : IGroupA, IGroupB, IGroupC
{
  ...
}

public class ClientInGroupA
{
  // Only dependent on methods it uses
  private readonly IGroupA _oa;
  public ClientInGroupA(IGroupA oa)
  {
    _oa = oa;
  }
  ...
}

public class ClientInGroupB
{
  // Only dependent on methods it uses
  private readonly IGroupB _ob;
  public ClientInGroupB(IGroupB ob)
  {
    _ob = ob;
  }
  ...
}

public class ClientInGroupC
{
  // Only dependent on methods it uses
  private readonly IGroupC _oc;
  public ClientInGroupC(IGroupC oc)
  {
    _oc = oc;
  }
  ...
}

Summary

Applying the Interface Segregation Principle can help against code smells such as Needless Complexity, Needless Redundancy, Rigidity, and Viscosity. Even though it might not be possible to split up the implementation, it is possible to define thinner interfaces, streamlined towards the need of the clients.

Next up is the last part of SOLID, the Dependency Inversion Principle, which will help you design your interfaces in a way that is suitable for Object Oriented Design.

Lämna en kommentar

E-postadressen publiceras inte. Obligatoriska fält är märkta *