Skip to content

Interface Segregation Principle

Clients should not be forced to depend on methods they don't use.

The Interface Segregation Principle (ISP) states that clients should not be forced to depend on interfaces they don’t use. Instead of one fat interface, many small, specific interfaces are preferred.

This principle helps prevent the creation of “fat” or “bloated” interfaces that force implementing classes to provide empty implementations for methods they don’t need.

Diagram

The Interface Segregation Principle ensures that:

  • Interfaces are focused - Each interface has a single, well-defined purpose
  • No forced implementations - Classes don’t implement methods they don’t need
  • Better cohesion - Related methods are grouped together
  • Loose coupling - Clients depend only on what they actually use

In simple terms: Don’t force a class to implement methods it doesn’t need!

Consider a system with different types of workers. Let’s see what happens when we create a fat interface.

Diagram
bad_workers.py
from abc import ABC, abstractmethod
class Worker(ABC):
"""❌ Fat interface - violates ISP"""
@abstractmethod
def work(self):
"""All workers can work"""
pass
@abstractmethod
def eat(self):
"""❌ Problem: Not all workers eat!"""
pass
@abstractmethod
def sleep(self):
"""❌ Problem: Not all workers sleep!"""
pass
class HumanWorker(Worker):
"""Human worker - can do all three"""
def work(self):
print("Human is working...")
def eat(self):
print("Human is eating...")
def sleep(self):
print("Human is sleeping...")
class RobotWorker(Worker):
"""❌ Robot worker - forced to implement methods it doesn't need!"""
def work(self):
print("Robot is working...")
def eat(self):
"""❌ Robots don't eat! Empty implementation"""
raise NotImplementedError("Robots don't eat!")
def sleep(self):
"""❌ Robots don't sleep! Empty implementation"""
raise NotImplementedError("Robots don't sleep!")
# Usage
human = HumanWorker()
human.work() # ✅ Works
human.eat() # ✅ Works
human.sleep() # ✅ Works
robot = RobotWorker()
robot.work() # ✅ Works
robot.eat() # ❌ Breaks! Robots don't eat
robot.sleep() # ❌ Breaks! Robots don't sleep
Diagram
workers.py
from abc import ABC, abstractmethod
class Workable(ABC):
"""Focused interface - only work capability"""
@abstractmethod
def work(self):
"""All workers can work"""
pass
class Eatable(ABC):
"""Focused interface - only eating capability"""
@abstractmethod
def eat(self):
"""Only workers that eat implement this"""
pass
class Sleepable(ABC):
"""Focused interface - only sleeping capability"""
@abstractmethod
def sleep(self):
"""Only workers that sleep implement this"""
pass
class HumanWorker(Workable, Eatable, Sleepable):
"""Human implements all interfaces it needs"""
def work(self):
print("Human is working...")
def eat(self):
print("Human is eating...")
def sleep(self):
print("Human is sleeping...")
class RobotWorker(Workable):
"""Robot only implements what it needs - work"""
def work(self):
print("Robot is working...")
# No eat() or sleep() - correct!
# Usage - Clean and focused!
human = HumanWorker()
human.work() # ✅ Works
human.eat() # ✅ Works
human.sleep() # ✅ Works
robot = RobotWorker()
robot.work() # ✅ Works
# robot.eat() # ✅ Type error - prevents calling methods robot doesn't have
# robot.sleep() # ✅ Type error - prevents calling methods robot doesn't have

Why this follows ISP:

  • Each interface has a single, focused responsibility
  • Classes implement only the interfaces they need
  • No empty implementations or exceptions
  • Changes to one interface don’t affect unrelated classes

Consider a document management system with different types of devices that can interact with documents.

Diagram
bad_devices.py
from abc import ABC, abstractmethod
class Device(ABC):
"""❌ Fat interface - violates ISP"""
@abstractmethod
def print(self, document: str):
"""Print a document"""
pass
@abstractmethod
def scan(self) -> str:
"""Scan a document"""
pass
@abstractmethod
def fax(self, document: str):
"""Fax a document"""
pass
@abstractmethod
def email(self, document: str):
"""Email a document"""
pass
class Printer(Device):
"""Printer - can print, but not scan/fax/email"""
def print(self, document: str):
print(f"Printing: {document}")
def scan(self) -> str:
"""❌ Printer can't scan!"""
raise NotImplementedError("This printer cannot scan!")
def fax(self, document: str):
"""❌ Printer can't fax!"""
raise NotImplementedError("This printer cannot fax!")
def email(self, document: str):
"""❌ Printer can't email!"""
raise NotImplementedError("This printer cannot email!")
class Scanner(Device):
"""Scanner - can scan, but not print/fax/email"""
def print(self, document: str):
"""❌ Scanner can't print!"""
raise NotImplementedError("This scanner cannot print!")
def scan(self) -> str:
return f"Scanned: {document}"
def fax(self, document: str):
"""❌ Scanner can't fax!"""
raise NotImplementedError("This scanner cannot fax!")
def email(self, document: str):
"""❌ Scanner can't email!"""
raise NotImplementedError("This scanner cannot email!")
# Usage - Lots of exceptions!
printer = Printer()
printer.print("doc.pdf") # ✅ Works
printer.scan() # ❌ Raises exception!
Diagram
devices.py
from abc import ABC, abstractmethod
class Printable(ABC):
"""Focused interface - printing capability"""
@abstractmethod
def print(self, document: str):
"""Print a document"""
pass
class Scannable(ABC):
"""Focused interface - scanning capability"""
@abstractmethod
def scan(self) -> str:
"""Scan a document"""
pass
class Faxable(ABC):
"""Focused interface - faxing capability"""
@abstractmethod
def fax(self, document: str):
"""Fax a document"""
pass
class Emailable(ABC):
"""Focused interface - emailing capability"""
@abstractmethod
def email(self, document: str):
"""Email a document"""
pass
class Printer(Printable):
"""Printer only implements what it can do"""
def print(self, document: str):
print(f"Printing: {document}")
class Scanner(Scannable):
"""Scanner only implements what it can do"""
def scan(self) -> str:
return "Scanned document content"
class MultiFunctionDevice(Printable, Scannable, Faxable, Emailable):
"""Multi-function device implements all interfaces it supports"""
def print(self, document: str):
print(f"Printing: {document}")
def scan(self) -> str:
return "Scanned document content"
def fax(self, document: str):
print(f"Faxing: {document}")
def email(self, document: str):
print(f"Emailing: {document}")
# Helper functions that work with specific interfaces
def print_document(device: Printable, document: str):
"""Works with any Printable device"""
device.print(document)
def scan_document(device: Scannable) -> str:
"""Works with any Scannable device"""
return device.scan()
# Usage - Clean and type-safe!
printer = Printer()
scanner = Scanner()
multi_device = MultiFunctionDevice()
print_document(printer, "doc.pdf") # ✅ Works
print_document(multi_device, "doc.pdf") # ✅ Works
# print_document(scanner, "doc.pdf") # ✅ Type error - scanner can't print
scan_document(scanner) # ✅ Works
scan_document(multi_device) # ✅ Works
# scan_document(printer) # ✅ Type error - printer can't scan

Why this follows ISP:

  • Each interface represents a single capability
  • Devices implement only the interfaces they support
  • No empty implementations or exceptions
  • Type system prevents calling unsupported methods
  • Easy to add new capabilities without affecting existing devices

Remember: The Interface Segregation Principle ensures that classes only depend on methods they actually use, leading to cleaner and more maintainable code! 🎯