INTERFACE SEGREGATION PRINCIPLE

dev.to

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; }
Enter fullscreen mode Exit fullscreen mode

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.");
  }
}
Enter fullscreen mode Exit fullscreen mode

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!"); }
}
Enter fullscreen mode Exit fullscreen mode

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?

Source: dev.to

arrow_back Back to News