Interface Segregation Principle Real Examples - Design patterns

Interface Segregation Principle Real Examples - Design patterns

Interface Segregation Principle Real ExamplePicture this: You walk into a restaurant and order a simple grilled cheese sandwich. But instead of just getting your sandwich, the waiter forces you to also take a 10-course tasting menu, a wine pairing, dessert, and a cooking class — even though you just wanted melted cheese on bread. You’re stuck paying for (and carrying) stuff you’ll never use.

That’s exactly what happens when you violate the Interface Segregation Principle (ISP). You force classes to implement methods they don’t need, can’t use, or will never call. The result? Bloated interfaces, confused developers, and code that lies about what it actually does. Today, we’re learning why smaller, focused interfaces beat one massive “do everything” interface every single time.

🤔 Why Should You Care About ISP?

Without ISP (The Nightmare):

  • Classes implement methods they’ll never use
  • Empty methods everywhere that do nothing
  • Misleading code (it says it can swipe, but actually can’t)
  • Changes to one part break unrelated classes
  • Testing becomes confusing (what actually works?)

With ISP (The Dream):

  • Classes only implement what they need
  • Clear contracts about capabilities
  • No fake methods pretending to work
  • Changes stay isolated
  • Code reads like honest documentation

Think of ISP as ordering à la carte instead of being forced into a fixed menu you don’t want.


📱 The Golden Rule of ISP

“No client should be forced to depend on methods it does not use.”

Translation: Don’t make classes implement features they don’t have. Break big interfaces into smaller, focused ones that clients can pick and choose from.


🚨 The Problem: The Bloated Interface

Let’s see disaster in action. Meet the Mobile interface that assumes every phone is a smartphone:

class Mobile:
def call(self):
raise NotImplementedError

def play(self):
raise NotImplementedError

def swipe(self):
raise NotImplementedError

Looks reasonable for modern phones, right? But watch what happens when we try to implement different types of phones:

The Smartphone (Works Fine)

class SmartPhone(Mobile):
def call(self):
print("Calling via VoIP...")

def play(self):
print("Playing Spotify...")

def swipe(self):
print("Swiping through apps...")

Perfect! Smartphones have all these features. No problem here.

The Nokia (The Problem Emerges)

class NokiaPhone(Mobile):
def call(self):
print("Making a phone call...")

def play(self):
print("Playing Snake game...")

def swipe(self):
# Wait... Nokia phones don't have touchscreens!
# But we're FORCED to implement this method
pass # Does nothing, but has to exist

See the issue? The NokiaPhone class has a swipe() method that does absolutely nothing. It's lying! From the outside, someone might think, "Oh, this phone can swipe!" But nope—it's an empty promise.

The real-world pain:

# Developer tries to use it
nokia = NokiaPhone()
nokia.swipe() # Nothing happens... why?

# Or worse, they check if method exists
if hasattr(nokia, 'swipe'):
print("This phone supports touch!") # FALSE! It doesn't!

✅ The Solution: Segregated Interfaces

Instead of one bloated interface, let’s create small, focused interfaces that describe specific capabilities:

from abc import ABC, abstractmethod

class TouchScreen(ABC):
@abstractmethod
def swipe(self, direction):
pass

class Player(ABC):
@abstractmethod
def play(self, audio):
pass

class Call(ABC):
@abstractmethod
def call(self, number):
pass

Now the magic happens! Each interface represents ONE capability. Classes can mix and match:

Modern Smartphone (All Features)

class SmartPhone(Player, Call, TouchScreen):
def call(self, number):
print(f"Calling {number} via app...")

def play(self, audio):
print(f"Playing {audio} on Spotify...")

def swipe(self, direction):
print(f"Swiping {direction}...")

Nokia Phone (Only What It Needs)

class Nokia(Player, Call):
def call(self, number):
print(f"Dialing {number}...")

def play(self, audio):
print(f"Playing {audio} on built-in player...")

# No swipe method! Because Nokia doesn't have touchscreen!

Portable Radio (Just Audio)

class Radio(Player):
def play(self, audio):
print(f"Broadcasting {audio}...")

# No call, no swipe—just plays audio!

Beautiful! Each class only implements what it actually does. No fake methods. No lying code.


🎯 Real-World Benefits

Before ISP:

nokia = NokiaPhone()
# Has swipe() method but does nothing
# Misleading and confusing!

if hasattr(nokia, 'swipe'):
nokia.swipe() # Silent failure-does nothing

After ISP:

nokia = Nokia()
# No swipe() method at all

if isinstance(nokia, TouchScreen):
nokia.swipe("right") # This check fails-correctly!
else:
print("This phone doesn't support touch") # Honest!

💡 Advanced Pattern: Composition

You can also combine ISP with composition for even more flexibility:

# Original fat interface (for backward compatibility)
class Mobile(Player, Call, TouchScreen):
@abstractmethod
def call(self):
pass

@abstractmethod
def play(self):
pass

@abstractmethod
def swipe(self):
pass
# Smart implementation using composition
class SmartPhone(Mobile):
def __init__(self, player, caller, touch_screen):
self.player = player
self.caller = caller
self.touch_screen = touch_screen

def play(self, audio):
self.player.play(audio)

def call(self, number):
self.caller.call(number)

def swipe(self, direction):
self.touch_screen.swipe(direction)


# Concrete implementations
class SpotifyPlayer:
def play(self, audio):
print(f"Streaming {audio} from Spotify")

class VoIPCaller:
def call(self, number):
print(f"VoIP calling {number}")

class MultiTouchScreen:
def swipe(self, direction):
print(f"Multi-touch swipe {direction}")

# Usage
phone = SmartPhone(
SpotifyPlayer(),
VoIPCaller(),
MultiTouchScreen()
)

phone.play("Lose yourself by Eminem")
phone.swipe("left")

Why this rocks: You can swap implementations! Want a different music player? Just pass a different Playerimplementation. No changes to SmartPhone class needed.


🚀 Real-World Examples

Example 1: Document Processors

❌ Bad (Violates ISP):

class Document:
def print(self): pass
def scan(self): pass
def fax(self): pass
def email(self): pass

class SimplePrinter(Document):
def print(self):
print("Printing...")

def scan(self):
pass # Can't scan!

def fax(self):
pass # Can't fax!

def email(self):
pass # Can't email!

✅ Good (Follows ISP):

class Printer(ABC):
@abstractmethod
def print(self): pass

class Scanner(ABC):
@abstractmethod
def scan(self): pass

class FaxMachine(ABC):
@abstractmethod
def fax(self): pass

class EmailSender(ABC):
@abstractmethod
def email(self): pass

# Simple printer
class SimplePrinter(Printer):
def print(self):
print("Printing...")

# All-in-one machine
class AllInOnePrinter(Printer, Scanner, FaxMachine, EmailSender):
def print(self): print("Printing...")
def scan(self): print("Scanning...")
def fax(self): print("Faxing...")
def email(self): print("Emailing...")

📚 TLDR Cheat Sheet

✅ ISP Rule: Clients shouldn’t depend on methods they don’t use
✅ Problem: Fat interfaces force unnecessary implementations
✅ Solution: Break interfaces into small, focused contracts
✅ Benefit: Classes only implement what they actually need
✅ Red flag: Empty methods with pass statements
✅ Best practice: Multiple small interfaces > one giant interface


Apply ISP today: Look for classes with empty methods that do nothing. That’s your signal to split the interface! Your code will be more honest, more flexible, and way easier to understand. 📱

Got a fat interface story? Share your ISP struggles below! 💻✨

Post a Comment

Previous Post Next Post