LESSON 2.4
Interface segregation
Callers should not depend on methods they do not use. One fat interface forces every implementor to stub methods it does not need.
6 min read
A Logger interface starts small: log(message). Over time it grows to include rotate(), compress(), sendAlert() and flush(). A simple ConsoleLogger now must stub out four methods it will never use, and every new method added to Logger is a breaking change for every logger in the codebase.
The problem
// This interface grew from one method to five. Every implementor
// must now implement or stub all five — even ConsoleLogger.
class Logger {
log(msg) { throw new Error("not implemented"); }
rotate() { throw new Error("not implemented"); }
compress() { throw new Error("not implemented"); }
sendAlert(msg) { throw new Error("not implemented"); }
flush() { throw new Error("not implemented"); }
}
class ConsoleLogger extends Logger {
log(msg) { console.log(msg); }
// rotate, compress, sendAlert, flush: useless stubs.
}Adding archive() to Logger breaks every class that extends it — including ConsoleLogger, which has nothing to do with archiving.
The rule
Interface segregation principle: clients should not be forced to depend on methods they do not use. Split one fat interface into several small, role-specific interfaces.
Each small interface describes one capability. A class implements the interfaces it genuinely supports. A caller depends only on the interface it uses.
In code
When to split and when not to
ISP costs one interface name per role. That name is the abstraction boundary and should represent a real domain concept: Writable, Rotatable, Alertable. Splitting for its own sake produces LoggerWritingCapability and similar monstrosities.
The practical test: if adding a method to an interface requires changing a class that has nothing to do with the new method, the interface is too fat.
Where you have already seen it
Java 8's Iterable, Comparable, and Closeable are each one method. Runnable is one method. The standard library avoided one large Object interface precisely because callers want to depend on exactly the capability they need. Node's stream.Readable and stream.Writable are separate for the same reason.
Try it
Logging Framework LLD
Give the logging framework separate contracts for writing, filtering and formatting before adding a new sink type.