• 4 hours
  • Easy

Free online content available in this course.

course.header.alt.is_video

course.header.alt.is_certifying

Got it!

Last updated on 3/28/24

“I” for the Interface Segregation Principle

Interface Segregation Principle (ISP)

Software engineer Robert C. Martin was consulting for Xerox when in 1996 he defined the integration segregation principle:

Clients should not be forced to depend upon interfaces that they do not use.

This principle is related to the single responsibility principle in that even interfaces have to have a single purpose. Unfortunately, it’s easy to violate this principle as you add more features to your application. 

The goal is to reduce the number of times you have to modify your code whenever a new feature or modification is needed. By splitting your application into multiple, independent components, you can reduce the number of modifications.

Violating ISP

The risk of making breaking-changes increases as your application grows over time. A new method can be added to an existing interface even though it implements a different responsibility. Therefore, it creates a polluted interface, which leads to bloated, fat interfaces that contain methods implementing several responsibilities. 

Let’s look at the following example:

public interface ICalculator
{
    int Add(int num1, int num2);
 
    int Subtract(int num1, int num2);
 
    int Multiply(int num1, int num2);
 
    int Divide(int num1, int num2);
 
    double Power(int number, double power);
 
    double SquareRoot(double number);
}
 
public class BasicCalculator : ICalculator
{
    public int Add(int num1, int num2)
    {
        return num1 + num2;
    }
 
    public int Subtract (int num1, int num2)
    {
        return num1 - num2;
    }
 
    public int Multiply (int num1, int num2)
    {
        return num1 * num2;
    }
 
    public int Divide (int num1, int num2)
    {
        return num1 / num2;
    }
 
    public double Power (int number, double power)
    {
        throw new NotSupportedException();
    }
 
    public double SquareRoot (double number)
    {
        throw new NotSupportedException();
    }
}
 
public class AdvancedCalculator : ICalculator
{
    public int Add(int num1, int num2)
    {
        return num1 + num2;
    }
 
    public int Subtract (int num1, int num2)
    {
        return num1 - num2;
    }
 
    public int Multiply (int num1, int num2)
    {
        return num1 * num2;
    }
 
    public int Divide (int num1, int num2)
    {
        return num1 / num2;
    }
 
    public double Power (int number, double power)
    {
        return Math.Pow(number, power);
    }
 
    public double SquareRoot (double number)
    {
        return Math.Sqrt(number);
    }
}
 
public class BasicMathStudent
{
    private BasicCalculator Calculator;
 
    public BasicMathStudent()
    {
        this.Calculator = new BasicCalculator();
    }
 
    public double Calculate(string operation, int operand1, int operand2)
    {
        switch (operation.ToLower())
        {
            case "add":
                return this.Calculator.Add(operand1, operand2);
            case "subtract":
                return this.Calculator.Subtract(operand1, operand2);
            case "multiply":
                return this.Calculator.Multiply(operand1, operand2);
            case "divide":
                return this.Calculator.Divide(operand1, operand2);
            default:
                throw new ArgumentException();
        }
    }
}
 
public class AdvancedMathStudent
{
    private AdvancedCalculator Calculator;
 
    public AdvancedMathStudent()
    {
        this.Calculator = new AdvancedCalculator();
    }
 
    public double Calculate(string operation, int number)
    {
        if (operation.ToLower() == "squareroot")
        {
            return this.Calculator.SquareRoot(number);
        }
        else
        {
            throw new ArgumentException();
        }
    }
 
    public double Calculate(string operation, int operand1, int operand2)
    {
        switch (operation.ToLower())
        {
            case "add":
                return this.Calculator.Add(operand1, operand2);
            case "subtract":
                return this.Calculator.Subtract(operand1, operand2);
            case "multiply":
                return this.Calculator.Multiply(operand1, operand2);
            case "divide":
                return this.Calculator.Divide(operand1, operand2);
            case "power":
                return this.Calculator.Power(operand1, operand2);
            default:
                throw new ArgumentException();
        }
    }
}

In this example, there are two calculations for each type of student:  BasicMathStudent  and  AdvancedMathStudent. The  BasicMathStudent  uses the  BasicCalculator, while the  AdvancedMathStudent  uses the  AdvancedCalculator. Both types of calculators implement the  ICalculator  interface.

If I run this code, it works fine. What’s the problem? Both calculators are implementing the  ICalculator  interface. Those methods inside the  ICalculator  are what calculators do, right?

Correct. But, take a closer look at the  BasicCalculator  class. Notice that it is polluted by having to implement the  Power  and  SquareRoot  methods. The  BasicCalculator  doesn’t need these methods, so why does it have to implement them? The  BasicMathStudent  is not expected to deal with those two methods and doesn’t need a calculator with those functions. 

It only has to implement two methods, and it will throw an error anyway if someone calls them. What’s the big deal?

Well, think about this over time as the application grows. What happens if the  BasicCalculator  or  BasicMathStudent  has subclasses? Those unnecessary methods that are polluting the interface get passed on.

Also, what happens if you want to add more functionality to the  AdvancedCalculator  like Cos, Sin, and Tan? You would then have to update the  ICalculator  interface, which in turn means adding more unnecessary methods to the  BasicCalculator.

Remove the Pollution: Segregate

How do you fix this issue with a bloated, fat interface? I gave you the answer earlier: ISP is related to the single responsibility principle. Let’s break down the  ICalculator  into more specific roles. 

Fix the interface definitions by leaving the rest of the application unchanged.

How?

Take the  ICalculator  interface and segregate it into two new interfaces:

public interface IArithmetic
{
    int Add(int num1, int num2);
 
    int Subtract(int num1, int num2);
 
    int Multiply(int num1, int num2);
 
    int Divide(int num1, int num2);
}
 
public interface IExponents
{
    double Power(int num, double power);
 
    double SquareRoot(double num);
}

Then change the BasicCalculator from:

public class BasicCalculator : ICalculator

To this:

public class BasicCalculator : IArithmetic

By doing so, you have removed the pollution from the  BasicCalculator  because it’s not implementing any methods it will never use.

Now, change the  AdvancedCalculator from:

public class AdvancedCalculator : ICalculator

To this:

public class AdvancedCalculator : IArithmetic, IExponents

Now, the  AdvancedCalculator  implements both the arithmetic and exponent features from these interfaces that it will use.

By keeping your interfaces lean and trim, you can prevent a lot of pollution from bloated interfaces.

Let’s Recap!

  • The interface segregation principle (ISP) states that “clients should not be forced to depend upon interfaces that they do not use.”

  • The intent of ISP is to prevent interface pollution known as fat interfaces.

  • Interface pollution is when an object implements an interface that has unnecessary behaviors.

  • Break fat interfaces down by segregating the responsibilities. 

  • Using ISP increases long-term flexibility and maintainability of your application.

Now for the last letter in SOLID, “D,” for the dependency inversion principle.

Example of certificate of achievement
Example of certificate of achievement