No class should be dependent on methods it doesn't need.
An example would be a "Machine" interface that forces the print(), scan(), and fax() methods. Not every machine has these 3 methods.
So we create the IPrinter, IScanner, and IFax interfaces. Now a machine signs only the contracts it actually needs and fulfills only those.
Code Example (Optional - Illustrating the text):
// ❌ Anti-pattern: Generic interface forcing unused methods
interface IMachine {
print(): void;
scan(): void;
fax(): void;
}
// ✅ SOLID: Segregated and specific interfaces
interface IPrinter { print(): void; }
interface IScanner { scan(): void; }
interface IFax { fax(): void; }
THE DEFINITION
In practice, we can understand ISP as the concept that a class should never depend on methods it doesn't use.
The interface must be reusable in several cases, so a rigid interface with excessive methods hinders this process.
When a class signs a giant contract, it is forced to implement methods that make no sense for its context, polluting the code and generating unnecessary coupling.
A BAD EXAMPLE
Context: A blog system where every user signs the same contract.
Here we violate some of the concepts I mentioned.
Code Example:
// BAD: Fat interface. Forces unnecessary behaviors.
interface IUserActions {
readPost(): void;
writePost(): void;
banUser(): void; // A regular user shouldn't see this!
}
class StandardUser implements IUserActions {
public readPost(): void { console.log("Reading..."); }
public writePost(): void { console.log("Writing..."); }
// ERROR: The regular user is forced to have this method
public banUser(): void {
throw new Error("Access Denied: You are not an administrator.");
}
}
DIVIDE AND CONQUER
The solution from the point of view of ISP is clear: break this huge interface into small, well-scoped contracts.
When more than one contract is needed, a new contract can be built with that specific combination, or the subscriber itself can assume more than one contract at a time (depending on the language).
A GOOD EXAMPLE
Same context, but with much better defined scopes.
As can be seen, only what is necessary is signed.
Code Example:
// GOOD: Small and specific interfaces.
interface IReader { readPost(): void; }
interface IWriter { writePost(): void; }
interface IAdmin { banUser(): void; }
// The regular user implements only what they actually do
class StandardUser implements IReader, IWriter {
public readPost(): void { console.log("Reading..."); }
public writePost(): void { console.log("Writing..."); }
}
// The Admin signs all contracts
class AdminUser implements IReader, IWriter, IAdmin {
public readPost(): void { console.log("Reading..."); }
public writePost(): void { console.log("Writing..."); }
public banUser(): void { console.log("User banned!"); }
}
IN THE LONG RUN
Segregating interfaces leaves your code highly modular. If tomorrow the banning rules of an IAdmin change, the StandardUser class will not need to be touched or recompiled.
Your system is then built like Lego blocks, fitting together only the pieces (contracts) that make sense for that structure.
My Links
Github: victor-lis-bronzo
Linkedin: victor-lis-bronzo
Portfolio: portfolio.victorlisbronzo.me
Coolest Portfolio: victorlisbronzo.me
Leave your reaction ❤️
Do you have the habit of creating several small interfaces or do you end up creating a single one for everything?