Single Responsibility Principle (SRP)
A class should have only one reason to change. It should do exactly one job.
Violates SRP
class UserService:
def validate(self, user): pass
def send_email(self, user): passFollows SRP
class UserValidator:
def validate(self, user):
pass
class EmailSender:
def send(self, user):
passEach class handles only one responsibility. Validation, email sending, and persistence are separated.
Open/Close Responsible (OCP)
Open for extension, closed for modifications
def process_payment(type):
if type == "card":
print("card payment")
elif type == "upi":
print("upi payment")from abc import ABC, abstractmethod
class Payment(ABC):
@abstractmethod
def process(self):
pass
class CardPayment(Payment):
def process(self):
print("paid using card")
class UpiPayment(Payment):
def process(self):
print("paid using upi")
def process(payment: Payment):
payment.process()To add a new payment type, I create a new class. I don’t modify existing logic. That’s OCP.
Liskov Substitution Principle (LSP)
LSP means subclasses should not break the behavior promised by the parent class. For example, a
PenguinextendingBirdviolates LSP because it breaks the expectation that all birds can fly.
Example for LSP Violation
class Bird:
def fly(self):
return "flying"
class Sparrow(Bird):
pass
class Eagle(Bird):
pass
class Penguin(Bird):
def fly(self):
raise Exception("Penguins can't fly")Interface Segregation Principle (ISP)
ISP means we should split large interfaces into smaller ones so that classes only implement what they actually use. For example, Robots shouldn’t be forced to implement an eat() method.
from abc import ABC, abstractmethod
class Worker(ABC):
@abstractmethod
def work(self):
pass
@abstractmethod
def eat(self):
pass
class Robot(Worker):
def work(self):
print("robot working")
def eat(self):
raise NotImplementedError("robot doesn't eat")class Workable(ABC):
@abstractmethod
def work(self):
pass
class Eatable(ABC):
@abstractmethod
def eat(self):
pass
class Human(Workable, Eatable):
def work(self):
print("human working")
def eat(self):
print("human eating")
class Robot(Workable):
def work(self):
print("robot working")
Dependency Inversion Principle (DIP)
High-level modules should not depend on low-level modules. Both should depend on abstractions.
class FileLogger:
def log(self, msg):
print(f"file log: {msg}")
class UserService:
def __init__(self):
self.logger = FileLogger() # tightly coupled
def create_user(self, user):
self.logger.log("user created")
from abc import ABC, abstractmethod
class Logger(ABC):
@abstractmethod
def log(self, msg):
pass
class FileLogger(Logger):
def log(self, msg):
print(f"file log: {msg}")
class DBLogger(Logger):
def log(self, msg):
print(f"db log: {msg}")
class UserService:
def __init__(self, logger: Logger): # injected dependency
self.logger = logger
def create_user(self, user):
self.logger.log("user created")
DIP means high-level modules depend on abstractions, not concrete classes. For example, UserService should accept a Logger interface instead of creating a FileLogger inside.