ISP - The Interface Segregation Principle

ISP - The Interface Segregation Principle

The Interface Segregation Principle is a part of SOLID, a mnemonic acronym that bundles a total of 5 design principles.

It is often associated with clean code.

But what exactly is it, is it important to you, should you even care?

What does it state?

The Interface Segregation Principle (ISP) sets a pretty simple statement:

"No client should be forced to depend on methods it does not use."

The ISP applied to the extreme will result in single-method interfaces, also called role interfaces.

You can take a look at the following example to get an idea of how the ISP can be violated in one way (it's in TypeScript).

interface ReportService {
  createEmployeeReport(employeeId: string): Report;
  createCustomerReport(customerId: string): Report;
  createManagementReport(projectId: string): Report;
}

class EmployeeReportService implements ReportService {
  createEmployeeReport(employeeId: string): Report {
    return {
      // ...
    };
  }
  createCustomerReport(customerId: string): Report {
    throw new Error("Method not implemented.");
  }
  createManagementReport(projectId: string): Report {
    throw new Error("Method not implemented.");
  }
}

Can you spot the issue?

The class implementing the interface actually doesn't want to implement two of the three methods defined within the interface. But it is forced to do so because the interface is too bulky.

The client in this case is you, satisfying the interface.

What does it try to prevent?

Most importantly it tries to prevent:

  • Confusion among client developers
  • Sprawling maintenance costs

But we'll take a look to find out what this actually means now.

ℹ️ Confusion among client developers

Imagine you had to implement such a bulky interface, like above, although you only wanted to use a base class to implement employee reports.

Wouldn't you be confused that you also had to implement so much more? I would be.

It's also a sign that the base class maybe tries to do too much.

Nevertheless, you are left with the need to actually do something about the methods you didn't intend to use at all. And even the thoughts you have to put into thinking about what to do are unnecessary work.

ℹ️ Sprawling maintenance costs

Every time the interface changes, several classes implementing it need to change. And in this case, it doesn't even matter if they actually implement all of the methods. They are forced to if the client wants their code to compile or at least run error-free at runtime.

This is a huge addition to maintenance costs. And this is a driver of unnecessary work for every client developer.

Fixing the issue

One way to fix the issue can now be to actually pull the interface apart, as shown below.

interface EmployeeReportService {
  createEmployeeReport(employeeId: string): Report;
}

interface CustomerReportService {
  createCustomerReport(customerId: string): Report;
}

interface ManagementReportService {
  createManagementReport(projectId: string): Report;
}

class EmployeeReportServiceImplementation implements EmployeeReportService {
  createEmployeeReport(employeeId: string): Report {
    // ...
  }
}

// and so on...

This is the extreme version of the ISP applied, role interfaces which all do exactly one thing.

With a little more thought, however, you can fix the issue even better, by generalizing the interface.

Take a look at the version below. Any notions of specifics were removed due to the fact that you now have a pretty generic interface, which can be implemented in many ways.

interface ReportService {
  createReport(id: string): Report;
}

class EmployeeReportService implements ReportService {
  createReport(id: string): Report {
    // ...
  }
}

// and so on...

Should you care?

I'm pretty sure you already know the answer => Yes, you should.

Bulky interfaces can occur in any language, even in dynamically typed ones like JavaScript. Although you don't have interfaces at hand, you still define, by the code you write, what you expect a client to pass to you.

Just imagine a function that takes a god object and uses several methods on that object to do its work. Everyone using your function would have to put at least something into those object's methods to make your function work.

The ISP saves you and others from putting in a lot of unnecessary work into the implementation of unwanted dependencies. And as with nearly any other principle associated with clean code, code becomes more readable and tests become better as well as more maintainable.

Before you leave

If you like my content, visit me on Twitter, and perhaps you’ll like what you see!