Single Responsibility Principle

Topics Covered

What Is Single Responsibility

The God Class Anti-Pattern

SRP Is About People, Not Code

What "One Responsibility" Actually Looks Like

Identifying Reasons to Change

Technique 1: The Stakeholder Test

Technique 2: The Description Test

Technique 3: The Import Test

Technique 4: The Change Frequency Test

Extracting Responsibilities

Step 1: Identify Method Clusters

Step 2: Create Focused Classes

Step 3: Compose with a Coordinator

When Not to Extract

SRP in Practice

SRP and Testing

SRP and Code Reviews

Common SRP Mistakes

Practice Problems

The Single Responsibility Principle (SRP) is the most misunderstood of the SOLID principles. Developers hear "a class should do one thing" and create classes with a single method. That is not what SRP means. SRP says: a class should have one reason to change. The word "reason" refers to a stakeholder: a person or group who would request a change.

Why does this matter? Because when a class has multiple reasons to change, modifying it for one reason risks breaking it for another. A UserService that validates input, saves to a database, and sends welcome emails will change when the validation rules change, when the database schema changes, and when the email template changes. Three different stakeholders, three different change triggers, all in one class. A bug fix in the email template accidentally breaks database persistence. A schema migration corrupts the validation logic.

God class splitting into focused classes

The God Class Anti-Pattern

The opposite of SRP is the god class, a class that knows everything and does everything. God classes are seductive because they are easy to write. Need to add a feature? Just add a method to the god class. But they become maintenance nightmares:

python
1class UserManager:
2    def __init__(self, db_connection, smtp_server):
3        self.db = db_connection
4        self.smtp = smtp_server
5        self.cache = {}
6
7    def create_user(self, name, email, password):
8        # Validation logic
9        if not name or len(name) < 2:
10            raise ValueError("Name too short")
11        if "@" not in email:
12            raise ValueError("Invalid email")
13        if len(password) < 8:
14            raise ValueError("Password too short")
15
16        # Hashing logic
17        hashed = self._hash_password(password)
18
19        # Database persistence
20        self.db.execute(
21            "INSERT INTO users (name, email, password) VALUES (?, ?, ?)",
22            (name, email, hashed)
23        )
24        user_id = self.db.last_insert_id()
25
26        # Cache update
27        self.cache[user_id] = {"name": name, "email": email}
28
29        # Email notification
30        self.smtp.send(
31            to=email,
32            subject="Welcome!",
33            body=f"Hello {name}, your account is ready."
34        )
35
36        return user_id
37
38    def _hash_password(self, password):
39        import hashlib
40        return hashlib.sha256(password.encode()).hexdigest()
41
42    def generate_report(self, format_type):
43        users = self.db.execute("SELECT * FROM users")
44        if format_type == "csv":
45            return self._to_csv(users)
46        elif format_type == "json":
47            return self._to_json(users)
48
49    def _to_csv(self, users):
50        # CSV formatting logic
51        pass
52
53    def _to_json(self, users):
54        # JSON formatting logic
55        pass

This class has at least five responsibilities: input validation, password hashing, database persistence, cache management, email sending, and report generation. Six different reasons to change, six different stakeholders who might request changes.

Key Insight

The number of methods in a class does not determine whether it follows SRP. A class with ten methods that all serve the same purpose (managing user persistence) is fine. A class with two methods that serve different stakeholders (saving to database and formatting reports) violates SRP. Count reasons to change, not methods.

SRP Is About People, Not Code

Robert Martin, who formulated SRP, clarified that "reason to change" maps directly to stakeholders. The database team wants to change the persistence logic. The security team wants to change the password hashing algorithm. The marketing team wants to change the welcome email template. If these are different people with different schedules and priorities, their concerns should live in different classes.

When concerns are separated, changes are isolated. The marketing team updates the email template without touching persistence code. The database team changes the schema without risking the email system. Each team modifies only the class that belongs to their domain, and other teams are unaffected.

What "One Responsibility" Actually Looks Like

A class following SRP has a clear, single-sentence description of its purpose:

  • UserValidator, validates user input against business rules
  • UserRepository, persists and retrieves user data from the database
  • PasswordHasher, hashes and verifies passwords
  • WelcomeEmailer, sends welcome emails to new users
  • UserReportGenerator, produces user reports in various formats

Each class has one reason to change, one stakeholder, and one area of expertise. When you read the class name, you know exactly what it does and, equally important, what it does not do.