Documentation Index Fetch the complete documentation index at: https://resources.devweekends.com/llms.txt
Use this file to discover all available pages before exploring further.
🧩 The ISP Rule
“Clients should not be forced to depend on methods they don’t use.”
Imagine a Swiss Army knife 🔪 vs specialized tools :
Swiss Army knife: Has everything, but each tool is mediocre
Specialized tools: Each does one thing perfectly
ISP says: Don’t give a fish a bicycle interface! 🐟🚲
Simple Rule : Split big interfaces into small, focused ones. Classes only implement what they need!
The gym membership analogy: Imagine a gym that forces every member to sign up for swimming, weightlifting, yoga, boxing, AND rock climbing — even if you only want to lift weights. You are paying for (and contractually obligated to maintain eligibility for) services you never use. ISP says the gym should offer separate memberships: a weights pass, a swim pass, a yoga pass. You pick only what you need. In code, this means small, focused interfaces so that implementing classes are not burdened with methods that are irrelevant to them.
🚨 The Problem: Fat Interfaces
❌ BAD: One Giant Interface
from abc import ABC , abstractmethod
class Worker ( ABC ):
"""Every worker must do ALL of these... 😰"""
@abstractmethod
def work ( self ):
pass
@abstractmethod
def eat_lunch ( self ):
pass
@abstractmethod
def attend_meeting ( self ):
pass
@abstractmethod
def code ( self ):
pass
@abstractmethod
def design ( self ):
pass
@abstractmethod
def manage_team ( self ):
pass
class Developer ( Worker ):
def work ( self ):
print ( "Writing code..." )
def eat_lunch ( self ):
print ( "Eating pizza..." )
def attend_meeting ( self ):
print ( "In standup..." )
def code ( self ):
print ( "Coding Python..." )
def design ( self ):
# 😰 Developer doesn't design UI!
raise NotImplementedError ( "I'm not a designer!" )
def manage_team ( self ):
# 😰 Developer doesn't manage!
raise NotImplementedError ( "I'm not a manager!" )
class Robot ( Worker ):
def work ( self ):
print ( "Processing..." )
def eat_lunch ( self ):
# 😰 Robots don't eat!
raise NotImplementedError ( "I don't eat!" )
def attend_meeting ( self ):
# 😰 Robots don't attend meetings!
raise NotImplementedError ( "I don't attend meetings!" )
def code ( self ):
print ( "Auto-generating code..." )
def design ( self ):
raise NotImplementedError ( "I don't design!" )
def manage_team ( self ):
raise NotImplementedError ( "I don't manage!" )
# 🚨 Most methods throw exceptions - BAD!
✅ GOOD: Small, Focused Interfaces
from abc import ABC , abstractmethod
# Split into small, focused interfaces
class Workable ( ABC ):
@abstractmethod
def work ( self ):
pass
class Eatable ( ABC ):
@abstractmethod
def eat ( self ):
pass
class Meetable ( ABC ):
@abstractmethod
def attend_meeting ( self ):
pass
class Codeable ( ABC ):
@abstractmethod
def code ( self ):
pass
class Designable ( ABC ):
@abstractmethod
def design ( self ):
pass
class Manageable ( ABC ):
@abstractmethod
def manage_team ( self ):
pass
# DESIGN REASONING: Each interface represents one capability.
# Classes compose only the interfaces they genuinely support.
# No NotImplementedError, no dead methods, no lies in the API.
class Developer ( Workable , Eatable , Meetable , Codeable ):
def work ( self ):
print ( "💻 Working on features..." )
def eat ( self ):
print ( "🍕 Eating pizza..." )
def attend_meeting ( self ):
print ( "🗣️ In standup..." )
def code ( self ):
print ( "🐍 Writing Python..." )
class Designer ( Workable , Eatable , Meetable , Designable ):
def work ( self ):
print ( "🎨 Creating mockups..." )
def eat ( self ):
print ( "🥗 Eating salad..." )
def attend_meeting ( self ):
print ( "🗣️ In design review..." )
def design ( self ):
print ( "🖌️ Designing UI..." )
class Manager ( Workable , Eatable , Meetable , Manageable ):
def work ( self ):
print ( "📊 Planning sprints..." )
def eat ( self ):
print ( "☕ Coffee meeting..." )
def attend_meeting ( self ):
print ( "🗣️ Leading meeting..." )
def manage_team ( self ):
print ( "👥 Managing team..." )
class Robot ( Workable , Codeable ):
"""Robot only works and codes - no eating or meetings!"""
def work ( self ):
print ( "🤖 Processing tasks..." )
def code ( self ):
print ( "🤖 Auto-generating code..." )
# 🎉 No NotImplementedError needed!
# Each class only has methods it actually uses
🖨️ Classic Example: Printer Interfaces
❌ BAD: Multifunction Monster
class MultifunctionDevice ( ABC ):
"""All devices must do EVERYTHING!"""
@abstractmethod
def print_doc ( self , document ):
pass
@abstractmethod
def scan_doc ( self , document ):
pass
@abstractmethod
def fax_doc ( self , document ):
pass
@abstractmethod
def copy_doc ( self , document ):
pass
@abstractmethod
def email_doc ( self , document , email ):
pass
class OldPrinter ( MultifunctionDevice ):
"""Old printer can only print! 😢"""
def print_doc ( self , document ):
print ( f "🖨️ Printing: { document } " )
def scan_doc ( self , document ):
raise Exception ( "Can't scan!" ) # 😰
def fax_doc ( self , document ):
raise Exception ( "Can't fax!" ) # 😰
def copy_doc ( self , document ):
raise Exception ( "Can't copy!" ) # 😰
def email_doc ( self , document , email ):
raise Exception ( "Can't email!" ) # 😰
✅ GOOD: Segregated Interfaces
from abc import ABC , abstractmethod
class Printer ( ABC ):
@abstractmethod
def print_doc ( self , document ):
pass
class Scanner ( ABC ):
@abstractmethod
def scan_doc ( self ) -> str :
pass
class Fax ( ABC ):
@abstractmethod
def fax_doc ( self , document , number ):
pass
class Copier ( ABC ):
@abstractmethod
def copy_doc ( self , document ) -> str :
pass
class Emailer ( ABC ):
@abstractmethod
def email_doc ( self , document , email ):
pass
# Simple printer - only prints!
class BasicPrinter ( Printer ):
def print_doc ( self , document ):
print ( f "🖨️ Printing: { document } " )
# Scanner - only scans!
class BasicScanner ( Scanner ):
def scan_doc ( self ):
return "📄 Scanned document content"
# All-in-one for those who need everything
class AllInOnePrinter ( Printer , Scanner , Copier , Fax , Emailer ):
def print_doc ( self , document ):
print ( f "🖨️ Printing: { document } " )
def scan_doc ( self ):
print ( "📸 Scanning..." )
return "Scanned content"
def copy_doc ( self , document ):
print ( "📋 Copying..." )
return document
def fax_doc ( self , document , number ):
print ( f "📠 Faxing to { number } ..." )
def email_doc ( self , document , email ):
print ( f "📧 Emailing to { email } ..." )
# Functions request only what they need
def print_report ( printer : Printer, report ):
printer.print_doc(report)
def scan_and_save ( scanner : Scanner):
content = scanner.scan_doc()
return content
# Works with any device that can print!
print_report(BasicPrinter(), "Sales Report" )
print_report(AllInOnePrinter(), "Sales Report" )
# Works with any device that can scan!
scan_and_save(AllInOnePrinter())
# scan_and_save(BasicPrinter()) # ❌ Type error - BasicPrinter can't scan
🎮 Real Example: Game Characters
from abc import ABC , abstractmethod
# Segregated ability interfaces
class Movable ( ABC ):
@abstractmethod
def move ( self , direction ):
pass
class Attackable ( ABC ):
@abstractmethod
def attack ( self , target ):
pass
class Healable ( ABC ):
@abstractmethod
def heal ( self , target ):
pass
class Flyable ( ABC ):
@abstractmethod
def fly ( self ):
pass
class Swimmable ( ABC ):
@abstractmethod
def swim ( self ):
pass
class Castable ( ABC ):
@abstractmethod
def cast_spell ( self , spell , target ):
pass
# Characters implement only what they can do!
class Warrior ( Movable , Attackable ):
def move ( self , direction ):
print ( f "⚔️ Warrior walking { direction } " )
def attack ( self , target ):
print ( f "⚔️ Warrior slashes { target } !" )
class Mage ( Movable , Attackable , Castable , Healable ):
def move ( self , direction ):
print ( f "🧙 Mage walking { direction } " )
def attack ( self , target ):
print ( f "🧙 Mage hits { target } with staff!" )
def cast_spell ( self , spell , target ):
print ( f "✨ Mage casts { spell } on { target } !" )
def heal ( self , target ):
print ( f "💚 Mage heals { target } !" )
class Dragon ( Movable , Attackable , Flyable ):
def move ( self , direction ):
print ( f "🐉 Dragon stomping { direction } " )
def attack ( self , target ):
print ( f "🐉 Dragon breathes fire on { target } !" )
def fly ( self ):
print ( "🐉 Dragon soars into the sky!" )
class Fish ( Movable , Swimmable ):
def move ( self , direction ):
print ( f "🐟 Fish swimming { direction } " )
def swim ( self ):
print ( "🐟 Fish diving deep!" )
class Turret ( Attackable ): # Can't move!
def attack ( self , target ):
print ( f "🔫 Turret fires at { target } !" )
# Game engine uses specific interfaces
def process_flying_units ( units : list[Flyable]):
for unit in units:
unit.fly()
def process_attackers ( units : list[Attackable]):
for unit in units:
unit.attack( "enemy" )
# Usage
flying_units = [Dragon()] # Only things that can fly
process_flying_units(flying_units)
attackers = [Warrior(), Mage(), Dragon(), Turret()] # All attackers
process_attackers(attackers)
📱 Real Example: Mobile App Features
from abc import ABC , abstractmethod
# Feature interfaces
class Photographable ( ABC ):
@abstractmethod
def take_photo ( self ):
pass
class Callable ( ABC ):
@abstractmethod
def make_call ( self , number ):
pass
class Textable ( ABC ):
@abstractmethod
def send_text ( self , number , message ):
pass
class GPSEnabled ( ABC ):
@abstractmethod
def get_location ( self ):
pass
class BluetoothEnabled ( ABC ):
@abstractmethod
def connect_bluetooth ( self , device ):
pass
class NFCEnabled ( ABC ):
@abstractmethod
def nfc_pay ( self , amount ):
pass
# Different devices implement different features
class BasicPhone ( Callable , Textable ):
"""Old flip phone - calls and texts only!"""
def make_call ( self , number ):
print ( f "📞 Calling { number } ..." )
def send_text ( self , number , message ):
print ( f "💬 Texting { number } : { message } " )
class SmartPhone ( Callable , Textable , Photographable , GPSEnabled , BluetoothEnabled , NFCEnabled ):
"""Modern smartphone - has everything!"""
def make_call ( self , number ):
print ( f "📱 Video calling { number } ..." )
def send_text ( self , number , message ):
print ( f "💬 iMessaging { number } : { message } " )
def take_photo ( self ):
print ( "📸 Taking high-res photo!" )
def get_location ( self ):
return { "lat" : 40.7128 , "lng" : - 74.0060 }
def connect_bluetooth ( self , device ):
print ( f "🔵 Connecting to { device } ..." )
def nfc_pay ( self , amount ):
print ( f "💳 Paying $ { amount } via NFC..." )
class Camera ( Photographable ):
"""Dedicated camera - only takes photos!"""
def take_photo ( self ):
print ( "📷 Taking professional photo!" )
class GPSDevice ( GPSEnabled ):
"""Dedicated GPS - only navigation!"""
def get_location ( self ):
return { "lat" : 40.7128 , "lng" : - 74.0060 }
# Functions request only what they need
def take_photos ( devices : list[Photographable]):
for device in devices:
device.take_photo()
def navigate ( device : GPSEnabled):
loc = device.get_location()
print ( f "📍 You are at { loc } " )
# All of these work!
take_photos([SmartPhone(), Camera()]) # ✅
navigate(SmartPhone()) # ✅
navigate(GPSDevice()) # ✅
# navigate(BasicPhone()) # ❌ Type error - BasicPhone has no GPS
🔄 How to Apply ISP
Identify the fat interface
Look for interfaces with many methods that some implementations don’t need
Group related methods
Which methods are always used together?
Create focused interfaces
One interface per group of related methods
Update implementations
Each class implements only the interfaces it needs
🧪 Practice Exercise
Challenge: Fix the Vehicle Interface
This interface is too fat. Split it! class Vehicle ( ABC ):
@abstractmethod
def start_engine ( self ):
pass
@abstractmethod
def stop_engine ( self ):
pass
@abstractmethod
def accelerate ( self ):
pass
@abstractmethod
def brake ( self ):
pass
@abstractmethod
def fly ( self ):
pass
@abstractmethod
def sail ( self ):
pass
@abstractmethod
def submerge ( self ):
pass
@abstractmethod
def refuel ( self ):
pass
@abstractmethod
def recharge ( self ):
pass
class Car ( Vehicle ):
def fly ( self ):
raise NotImplementedError ( "Cars can't fly!" )
def sail ( self ):
raise NotImplementedError ( "Cars can't sail!" )
def submerge ( self ):
raise NotImplementedError ( "Cars can't submerge!" )
def recharge ( self ):
raise NotImplementedError ( "Gas cars don't recharge!" )
# ... lots of NotImplementedError
class Bicycle ( Vehicle ):
def start_engine ( self ):
raise NotImplementedError ( "Bicycles have no engine!" )
# ... even more NotImplementedError! 😱
📝 Key Takeaways
Small > Big Many small interfaces beat one big one
No Forced Methods Classes shouldn’t implement what they don’t need
Combine as Needed Classes can implement multiple interfaces
Client-Focused Interfaces should fit what clients actually need
Why ISP Matters in Production
In real codebases, fat interfaces cause a subtle but painful problem: forced coupling. If UserService depends on a DataStore interface with 30 methods (read, write, delete, batch, stream, replicate…), but UserService only ever calls read() and write(), it is still coupled to all 30 methods. When someone adds a 31st method to DataStore, UserService’s tests might break even though it uses none of the new functionality. ISP prevents this — by depending on a narrow ReadWriteStore interface with just 2 methods, UserService is insulated from changes it does not care about.
A senior engineer would say: “ISP is really about coupling management. The wider the interface you depend on, the more reasons your code has to change. Narrow interfaces minimize your exposure to other teams’ changes.”
Interview Insight
ISP shows up in interviews as the “what if we add a new feature?” question. When you design a system with a monolithic interface and the interviewer says “now support a device that can only do X but not Y,” your design either handles it gracefully or crumbles. If you split interfaces upfront, adding a printer-only device that does not scan is trivial: it implements Printable and ignores Scannable. If you used a fat MultiFunctionDevice interface, you are stuck with NotImplementedError. The ISP-aware design also connects to LSP — classes that implement an interface should actually support all its methods, and small interfaces make that achievable. When presenting your design, say: “I am keeping this interface narrow so implementors are not forced to support operations they cannot perform.”
🏃 Next: Dependency Inversion Principle
Let’s learn the final SOLID principle - how to depend on abstractions!
Continue to Dependency Inversion → Learn how to make your code flexible by depending on abstractions!