No client should be forced to depend on methods it does not use
The Interface Segregation Principle (ISP) states that no client should be forced to depend on methods it does not use. Essentially, it's better to have many small, specific interfaces than one large, general-purpose interface (often called a "fat" interface).
When interfaces are too large, implementing classes are forced to implement methods they don't need, often leading to empty implementations or throwing exceptions. This violates the principle and can make the system more rigid and harder to maintain.
Imagine an interface IMultiFunctionDevice
that includes methods for printing, scanning, and faxing.
// "Fat" Interface
interface IMultiFunctionDevice {
print(document);
scan(document);
fax(document);
}
// A simple printer that only prints
class SimplePrinter implements IMultiFunctionDevice {
print(document) {
console.log("Printing document...");
}
// Forced to implement scan and fax, even though it can't perform these actions.
scan(document) {
throw new Error("SimplePrinter cannot scan."); // Or empty implementation
}
fax(document) {
throw new Error("SimplePrinter cannot fax."); // Or empty implementation
}
}
// A dedicated scanner
class DedicatedScanner implements IMultiFunctionDevice {
print(document) {
throw new Error("DedicatedScanner cannot print.");
}
scan(document) {
console.log("Scanning document...");
}
fax(document) {
throw new Error("DedicatedScanner cannot fax.");
}
}
In this example, SimplePrinter
is forced to implement scan
and fax
methods it doesn't use. Similarly, DedicatedScanner
is forced to implement print
and fax
. This violates ISP because clients (the implementing classes) are forced to depend on methods they don't need.
We can fix the previous example by segregating the IMultiFunctionDevice
interface into smaller, more specific interfaces.
// Segregated Interfaces
interface IPrinter {
print(document);
}
interface IScanner {
scan(document);
}
interface IFax {
fax(document);
}
// Simple printer implements only IPrinter
class SimplePrinter implements IPrinter {
print(document) {
console.log("Printing document...");
}
}
// Photocopier implements IPrinter and IScanner
class Photocopier implements IPrinter, IScanner {
print(document) {
console.log("Photocopier printing document...");
}
scan(document) {
console.log("Photocopier scanning document...");
}
}
// Multi-function device implements all interfaces
class MultiFunctionMachine implements IPrinter, IScanner, IFax {
print(document) {
console.log("MultiFunctionMachine printing...");
}
scan(document) {
console.log("MultiFunctionMachine scanning...");
}
fax(document) {
console.log("MultiFunctionMachine faxing...");
}
}
By breaking down the large interface into smaller ones (IPrinter
, IScanner
, IFax
), each class now only implements the interfaces relevant to its capabilities. SimplePrinter
only needs IPrinter
. Photocopier
needs IPrinter
and IScanner
. No class is forced to implement methods it doesn't use.
Select a worker type to see which tasks they can perform based on segregated interfaces (IWork
, IEat
, IManage
).