Single Responsibility Principle Explained with Example

Single Responsibility Principle Explained with Example

Picture this: You hire a chef for your restaurant. Great! They cook amazing pasta. But then you ask them to also take orders, wash dishes, handle accounting, manage inventory, AND deliver food. Three weeks later, your chef quits, your kitchen is chaos, and your customers are eating burnt spaghetti while waiting 2 hours for their order.

That’s exactly what happens when you violate the Single Responsibility Principle (SRP). You create “super classes” that do everything, and when one thing breaks, EVERYTHING breaks. Today, we’re diving deep into why your classes should be like specialized chefs — masters of ONE thing, not mediocre at ten things.

🤔 Why Should You Care About SRP?

Without SRP (The Nightmare):

  • One bug fix breaks three unrelated features
  • Testing becomes impossible (too many responsibilities)
  • Code reviews take hours because everything is tangled
  • New team members cry when reading your code
  • Changes ripple through the entire codebase

With SRP (The Dream):

  • Each class has ONE clear purpose
  • Bugs are isolated and easy to fix
  • Testing is straightforward (one responsibility = one test suite)
  • Code reads like a well-organized library
  • Changes are surgical, not catastrophic

Think of SRP as Marie Kondo for your code — everything has its place, and nothing does more than it should.


🎯 The Golden Rule of SRP

“A class should have ONE, and ONLY ONE, reason to change.”

Translation: If you need to modify a class because of changes in database logic, user interface, business rules, AND email formatting — your class is doing too much.


🚨 The Problem: The God Object

Let’s start with what NOT to do. Meet the Library class from hell:

class Library:
def __init__(self):
self.books = []
self.count = 0
self.database_connection = None

def add_book(self, book):
self.books.append(book)
self.count += 1

def remove_book(self, book_name):
self.books.remove(book_name)
self.count -= 1

def add_to_cart(self, book_name, user):
# Shopping cart logic
cart_item = {"book": book_name, "user": user}
# Save to database
self.save_to_database(cart_item)

def register_user_to_book(self, book, user):
# User registration logic
registration = {"book": book, "user": user}
# Send email notification
self.send_email(user.email, "You're registered!")
# Update database
self.save_to_database(registration)

def save_to_database(self, data):
# Database operations
pass

def send_email(self, email, message):
# Email sending logic
pass

def generate_report(self):
# Report generation
return f"Total books: {self.count}"

What’s wrong here? Everything! This class is responsible for:

  1. Managing books (add/remove)
  2. Shopping cart functionality
  3. User registration
  4. Database operations
  5. Email notifications
  6. Report generation

That’s SIX different responsibilities! If email logic changes, you modify Library. If database changes, you modify Library. If cart logic changes… you get the idea.

This is called a “God Object” — a class that thinks it controls the universe. It’s the programming equivalent of your boss who insists on micromanaging every single task.


✅ The Solution: Separation of Concerns

Let’s fix this mess by giving each responsibility its own class:

1. Book Management (Core Responsibility)

class Library:
def __init__(self):
self.books = []

def add_book(self, book):
self.books.append(book)

def remove_book(self, book_name):
self.books.remove(book_name)

def find_book(self, book_name):
for book in self.books:
if book.name == book_name:
return book
return None

ONE job: Manage the collection of books. That’s it.

2. Shopping Cart (Separate Responsibility)

class ShoppingCart:
def __init__(self):
self.items = []

def add_item(self, book, user):
cart_item = {
"book": book,
"user": user,
"timestamp": datetime.now()
}
self.items.append(cart_item)

def remove_item(self, book):
self.items = [item for item in self.items
if item["book"] != book]

def get_total_items(self):
return len(self.items)

ONE job: Handle shopping cart operations.

3. User Registration (Separate Responsibility)

class UserRegistration:
def __init__(self, email_service, database_service):
self.email_service = email_service
self.database_service = database_service

def register_user_for_book(self, user, book):
registration_data = {
"user": user,
"book": book,
"date": datetime.now()
}

# Save registration
self.database_service.save(registration_data)

# Notify user
self.email_service.send(
user.email,
f"You're registered for {book.name}!"
)

ONE job: Handle user registration process.

4. Database Operations (Separate Responsibility)


class DatabaseService:
def __init__(self, connection_string):
self.connection = self.connect(connection_string)

def save(self, data):
# Database save logic
pass

def retrieve(self, query):
# Database retrieval logic
pass

def delete(self, identifier):
# Database deletion logic
pass

ONE job: Handle all database interactions.

5. Email Service (Separate Responsibility)

class EmailService:
def __init__(self, smtp_config):
self.smtp_config = smtp_config

def send(self, recipient, message):
# Email sending logic
pass

def send_bulk(self, recipients, message):
# Bulk email logic
pass

ONE job: Handle email communications.


🎉 Real-World Benefits

Before SRP (One Change = Multiple Risks):

Change email template → Modify Library class
Risk: Breaking book management, cart, registration
Test needed: ALL functionality

After SRP (One Change = One Location):

Change email template → Modify EmailService only
Risk: Only email functionality affected
Test needed: ONLY EmailService

💡 How to Apply SRP Today

Step 1: Identify Responsibilities

Look at your class and list what it does:

  • Does it manage data?
  • Does it handle I/O operations?
  • Does it perform business logic?
  • Does it communicate with external services?

Step 2: Ask “Why Would This Change?”

If you have more than one answer, you have multiple responsibilities:

  • “Database schema changes” → Database responsibility
  • “UI layout changes” → Presentation responsibility
  • “Business rules change” → Business logic responsibility

Step 3: Extract and Isolate

Create separate classes for each responsibility:

# Instead of one mega-class
class EverythingClass:
# 500 lines of mixed responsibilities
pass
# Create focused classes
class DataManager:
pass

class BusinessLogic:
pass

class UIController:
pass

🚀 Real-World Example: E-commerce Order

❌ Bad (Multiple Responsibilities):

class Order:
def calculate_total(self): pass
def save_to_database(self): pass
def send_confirmation_email(self): pass
def generate_invoice_pdf(self): pass
def process_payment(self): pass

✅ Good (Single Responsibilities):

class Order:
def calculate_total(self): pass
class OrderRepository:
def save(self, order):
pass

class EmailService:
def send_confirmation(self, order):
pass

class InvoiceGenerator:
def generate_pdf(self, order):
pass

class PaymentProcessor:
def process(self, order):
pass


📚 TLDR Cheat Sheet

✅ SRP Rule: One class = One responsibility = One reason to change
✅ God Objects: Classes doing everything are maintenance nightmares
✅ Benefits: Easier testing, debugging, and maintenance
✅ How to identify: Ask “Why would this class change?”
✅ Fix: Extract responsibilities into separate focused classes
✅ Real analogy: Restaurant with specialized roles vs one person doing everything


Start today: Pick your largest class. Count its responsibilities. If it’s more than one, it’s time to split! Your future self will thank you when bugs become easy to fix instead of week-long debugging sessions. 🎯

Have a God Object horror story? Share it below! Let’s learn from each other’s pain. 💻✨

Post a Comment

Previous Post Next Post