Avoiding Interface Pollution with the Interface Segregation Principle
The Benefits of Role Interfaces in SOLID Code
One of the themes that has popped up throughout our SOLID series is that of decoupling. In short, this theme argues that entities (objects, modules, functions, etc.) in a software program should be loosely coupled so as to prevent changes in one place from propagating to another. The reason this is desirable is that loosely coupled entities are easier to maintain, more flexible, and more mobile. We reviewed some of the reasons why this is the case in part 2 of the series, which covered the Open/Closed Principle, and in part 3, which covered the Liskov Substitution Principle. And yet, decoupling is so important that there is still more to say on the topic, namely, how to avoid so-called “interface pollution,” wherein classes are unnecessarily forced to implement behaviors that they don’t need. It is here that our next SOLID principle appears: the Interface Segregation Principle.
A Quick Refresher on SOLID
SOLID is an acronym for a set of five software development principles, which if followed, are intended to help developers create flexible and clean code. The five principles are:
- The Single Responsibility Principle — Classes should have a single responsibility and thus only a single reason to change.
- The Open/Closed Principle — Classes and other entities should be open for extension but closed for modification.
- The Liskov Substitution Principle — Objects should be replaceable by their subtypes.
- The Interface Segregation Principle — Interfaces should be client specific rather than general.
- The Dependency Inversion Principle — Depend on abstractions rather than concretions.
The Interface Segregation Principle
As we discussed in our review of the Open/Closed Principle, interfaces are a means of programming with abstractions rather than concretions. An interface serves as a kind of contract between two objects that interact with one another. Rather than depending directly on one another, each object instead depends on the intermediary interface. The client object (the one using another object’s behavior) doesn’t have any knowledge of how the service object (the one that implements some behavior) is structured. For its part, the service object merely guarantees that it will implement behavior described in the interface without bothering to reveal how it will do so. As a result, the two objects are effectively decoupled since neither has a direct dependency on the other. Further, interfaces allow for the creation of multiple service objects that all implement some guaranteed behavior, meaning that a client object can exploit many different behaviors depending on which service object is being used (and all without ever knowing that different kinds of service objects even exist.)
As useful as interfaces are, they raise an interesting conundrum: what happens when you want to create a service object that doesn’t actually need all of the behaviors defined on its interface? Because an interface is a contract, you would be forced to define behaviors that are effectively useless. This is known colloquially as “interface pollution” because a class may become polluted with behaviors that it doesn’t need. Worse yet, that pollution would propagate to any subclasses of a polluted superclass. This is a particularly insidious kind of coupling because it creates dependencies that don’t do anything even marginally useful.
As part of his SOLID principles, Robert C. Martin proposed a solution to this problem, which he called the Interface Segregation Principle (ISP) [1]. Martin argued that interface pollution was primarily the result of “fat interfaces” — that is, interfaces with a large number of prescribed methods. To counter the effects of fat interfaces, Martin defined the ISP as follows:
Clients should not be forced to depend upon interfaces that they do not use.
If fat interfaces are problematic, then what’s the alternative? Martin advocates for the use of so-called “role interfaces”, which are small interfaces that only contain methods that are of interest to the objects that use them. A fat interface may therefore be broken down into smaller role interfaces that guarantee specific related behaviors. Clients that require behaviors from multiple role interfaces may simply implement each of them. Meanwhile, clients that only need limited behaviors are not forced to live with unnecessary interface pollution. In other words, separate clients can and should have separate interfaces, which in turn limits coupling and cascading breakage.
Interface Pollution in Action
Interface pollution is ultimately a question of desired behavior. If an interface exclusively defines behaviors that an implementing object actually desires, then pollution won’t be a problem. However, if an interface is broad enough as to define behaviors that won’t be used universally, then it’s probably worth investigating options to break it down. Consider the following program.






