Interface Segregation
No client should depend on methods it doesn't use. Fat interfaces, the empty method smell, and splitting into focused role interfaces.
On this page
The Interface Segregation Principle is the smallest of the five and the easiest to apply: no client should be forced to depend on methods it doesn't use. Fat interfaces - the ones with fifteen methods where any given caller needs three - couple clients to code they never touch, so an unrelated change ripples where it has no business going. The fix is to split them into small, focused role interfaces.
The fat interface smell
ledger-legacy has a sprawling LedgerService interface that grew to serve everyone:
interface LedgerService {
void recordTransaction(Transaction t);
List<Transaction> findByAccount(String id);
BigDecimal calculateFees(String id);
String exportCsv(String id);
String exportPdf(String id);
void reindexSearch();
void purgeOldRecords(int days);
void runNightlyBatch();
}The reporting UI only needs findByAccount and exportPdf, but it depends on the whole interface - including
runNightlyBatch and purgeOldRecords. Consequences:
- A change to the batch method's signature forces a recompile/redeploy of clients that never call it.
- Test doubles are painful: to mock
LedgerServicefor a report test, you must stub eight methods you don't care about. - The interface lies about what each client actually needs, obscuring the real dependencies.
The empty-implementation tell
The clearest sign of an ISP violation is an implementation forced to stub methods it can't meaningfully support:
class ReadOnlyReportView implements LedgerService {
public List<Transaction> findByAccount(String id) { ... } // the one it needs
public void recordTransaction(Transaction t) {
throw new UnsupportedOperationException(); // 🚩 forced to implement, can't support
}
public void runNightlyBatch() { /* do nothing */ } // 🚩 meaningless here
// ...six more stubs...
}Those UnsupportedOperationExceptions and empty bodies are the interface telling you it bundles unrelated
roles - and note they're also LSP violations, since callers can't safely use those methods. Fat interfaces breed
Liskov problems.
Splitting into role interfaces
Separate the interface along the roles clients actually play:
interface TransactionRecorder { void recordTransaction(Transaction t); }
interface TransactionReader { List<Transaction> findByAccount(String id); }
interface LedgerExporter { String exportCsv(String id); String exportPdf(String id); }
interface LedgerMaintenance { void reindexSearch(); void purgeOldRecords(int days); void runNightlyBatch(); }Now the reporting UI depends on just TransactionReader + LedgerExporter - and nothing else can break it. A
class can still implement several of these if it genuinely fills those roles (a full service might implement
all four), but clients depend only on the narrow role they use. Interfaces are defined by the consumer's
needs, not the implementer's convenience.
ISP is SRP for interfaces
Notice the symmetry: SRP says a class should have one reason to change; ISP says an interface should serve one kind of client. A fat interface has many reasons to change (every client's needs), so segregating it is applying the single-responsibility idea to the contract, not the implementation. Small interfaces also make Dependency Inversion (next lesson) cleaner - you invert against a precise role, not a kitchen sink.
A fat interface is a universal remote with 60 buttons handed to every family member - the person who just wants to turn on the lamp must hold a device covered in buttons for the projector, the blinds, and the sound system, and if the projector firmware changes, the whole remote gets recalled. ISP is replacing it with simple labeled switches: the lamp switch does one thing, and whoever only needs light isn't affected when the projector is swapped. People depend on the switch for their job, not on a monolith of everyone's controls.
A Machine interface has print(doc), scan(doc), fax(doc), and staple(doc). A simple office printer
implements only print - so its class throws UnsupportedOperationException for the other three. A high-end
multifunction device implements all four. Show how ISP fixes the simple printer's problem, and explain why a
class implementing multiple small interfaces is fine.
What problem does the Interface Segregation Principle solve?
Key takeaways
- ISP: no client should be forced to depend on methods it doesn't use - keep interfaces small and role-focused.
- The tell of a fat interface is implementations stubbing methods with UnsupportedOperationException or empty bodies (also an LSP smell).
- Fat interfaces couple every client to every method, so an unrelated change forces recompiles and makes test doubles painful.
- Split by the roles clients actually play; a class may implement several small interfaces if it genuinely fills those roles.
- Define interfaces by the consumer's needs, not the implementer's convenience - ISP is essentially SRP applied to contracts.