Low-level design2 min

Interface Segregation Principle

The Interface Segregation Principle (ISP) states that no client should be forced to depend on methods it does not use.

This means you should not create massive "fat" interfaces that contain dozens of methods. Instead, you should break them down into smaller, highly cohesive, role-specific interfaces.

Real-Life Analogy

Think of a Restaurant Menu. Instead of forcing every customer to read a 100-page book containing Breakfast, Lunch, Dinner, Drinks, Desserts, and the Janitor's cleaning schedule (a "fat" interface), the restaurant provides specific menus. If you arrive at 8 AM, you get the Breakfast Menu. You aren't burdened with options you don't need.

Example: Violation of ISP

Imagine a massive Machine interface for an office printer/scanner/fax machine.

typescript
interface Machine {
  print(document: string): void;
  scan(document: string): void;
  fax(document: string): void;
}

// A modern all-in-one machine implements it fine
class MultiFunctionPrinter implements Machine {
  print(doc: string) { /* prints */ }
  scan(doc: string) { /* scans */ }
  fax(doc: string) { /* faxes */ }
}

// But an old, basic printer is FORCED to implement methods it can't use!
class OldPrinter implements Machine {
  print(doc: string) { /* prints */ }
  
  scan(doc: string) {
    throw new Error("I cannot scan!"); // Violation!
  }
  
  fax(doc: string) {
    throw new Error("I cannot fax!"); // Violation!
  }
}

Example: Proper ISP Design

Segregate the massive interface into smaller, specialized interfaces.

typescript
interface Printer {
  print(document: string): void;
}

interface Scanner {
  scan(document: string): void;
}

interface Fax {
  fax(document: string): void;
}

// The old printer only implements what it actually supports
class OldPrinter implements Printer {
  print(doc: string) { /* prints */ }
}

// The modern machine can implement multiple interfaces!
class MultiFunctionPrinter implements Printer, Scanner, Fax {
  print(doc: string) { /* prints */ }
  scan(doc: string) { /* scans */ }
  fax(doc: string) { /* faxes */ }
}

Why is it Important?

  • Decoupling: Changes to the Fax interface won't force the OldPrinter class to recompile or be updated.
  • Clarity: It's immediately obvious what capabilities a class has based on the small interfaces it implements.
  • Avoids Dummy Implementations: Prevents codebases from being polluted with throw new NotImplementedException() inside methods that shouldn't exist in the first place.