Interface Segregation Principle (ISP)
“Clients should not be forced to depend on methods they do not use.”
In essence, large interfaces should be broken into smaller, role-specific interfaces so that implementing classes only
need to know about the methods that matter to them.
Purpose:
- Avoids “fat” interfaces – by splitting broad interfaces into focused ones, no class is burdened with irrelevant methods.
- Reduces coupling and side effects: Changes to one part of an interface won’t impact classes that don’t use that part.
- Makes implementations simpler and more flexible. A class can implement multiple small interfaces as needed, rather
than one monolithic interface.
Minimal Example
Imagine a multi-function printer interface that includes printing, scanning, and faxing in one. A simple printer that
only prints would still be forced to implement (or stub out) scan() and fax() methods, which it doesn’t need – violating
ISP. Below, MultiFunctionDevice represents such a bloated interface and OldPrinter is needlessly forced to depend on
unused methods. The solution is to segregate the interfaces: separate Printer, Scanner, and Fax interfaces so that classes
implement only what they actually support.
# Big interface (violates ISP):
class MultiFunctionDevice:
def print(self, doc): raise NotImplementedError
def scan(self, doc): raise NotImplementedError
def fax(self, doc): raise NotImplementedError
class OldPrinter(MultiFunctionDevice):
def print(self, doc):
print("Printing:", doc)
def scan(self, doc):
pass # Not supported, but must implement
def fax(self, doc):
pass # Not supported, but must implement
# Interface segregation: smaller, specific interfaces
class Printer:
def print(self, doc): raise NotImplementedError
class Scanner:
def scan(self, doc): raise NotImplementedError
class Fax:
def fax(self, doc): raise NotImplementedError
class SimplePrinter(Printer):
def print(self, doc):
print("Printing:", doc)
# SimplePrinter only implements Printer, no need for scan/fax methods.
More Realistic Example
Building on the printing example, we can have distinct interfaces and classes for each capability. For instance,
PhotoPrinter implements Printer, OfficeScanner implements Scanner. A multi-function machine can be composed by combining
a printer and a scanner implementation without any class having to implement methods it doesn’t use. This demonstrates
how ISP enables flexible combinations of behaviors:
class PhotoPrinter(Printer):
def print(self, doc):
print("Printing photo:", doc)
class OfficeScanner(Scanner):
def scan(self, doc):
print("Scanning document:", doc)
class MultiFunctionMachine(Printer, Scanner):
def __init__(self, printer: Printer, scanner: Scanner):
self.printer = printer
self.scanner = scanner
def print(self, doc):
# Delegate to an internal Printer
self.printer.print(doc)
def scan(self, doc):
# Delegate to an internal Scanner
self.scanner.scan(doc)
# Using the segregated interfaces:
printer = PhotoPrinter()
scanner = OfficeScanner()
mfp = MultiFunctionMachine(printer, scanner) # compose a multi-function device
mfp.print("image.png") # Output: Printing photo: image.png
mfp.scan("file.txt") # Output: Scanning document: file.txt
# We could easily swap in different implementations for each interface as needed.
Responses are generated using AI and may contain mistakes.