Single Responsibility Principle

A class should have one, and only one, reason to change.

Overview

The Single Responsibility Principle (SRP) states that every module, class, or function should have responsibility over a single part of the functionality. This isn’t about doing “one thing” in a literal sense—it’s about having one reason to change. A “reason to change” typically maps to a stakeholder or actor whose requirements could cause you to modify the code.

When a component has multiple responsibilities, changes to one responsibility risk breaking behavior related to the other. SRP minimizes this coupling by ensuring that each component changes only in response to one kind of requirement change.

Key Principles

Identify the Actors

Think about who might request changes to a piece of code. A User class that handles both authentication logic and report formatting has two actors: the security team (authentication rules) and the reporting team (format requirements). These should be separate components.

// Two reasons to change: authentication rules OR profile display rules
class User {
  authenticate(password: string): boolean { ... }
  formatProfileForDisplay(): string { ... }
}
 
// Separated by responsibility
class UserAuthenticator {
  authenticate(user: User, password: string): boolean { ... }
}
 
class UserProfileFormatter {
  format(user: User): string { ... }
}

Cohesion as a Guide

Responsibilities are cohesive when they change together. If two pieces of functionality always change for the same reason, they likely belong together. If they change for different reasons, they should be separated.

Façade Pattern for Organization

When separating responsibilities creates many small classes, a façade can provide a simpler interface that delegates to specialized components internally.

// Façade providing unified access
class UserService {
  private authenticator = new UserAuthenticator();
  private formatter = new UserProfileFormatter();
  private repository = new UserRepository();
 
  authenticate(userId: string, password: string): boolean {
    const user = this.repository.find(userId);
    return this.authenticator.authenticate(user, password);
  }
 
  getProfileDisplay(userId: string): string {
    const user = this.repository.find(userId);
    return this.formatter.format(user);
  }
}

Example

Consider an Employee class that calculates pay, saves to database, and generates reports:

// Before: Three reasons to change
class Employee {
  calculatePay(): number {
    // Business rules from accounting
  }
 
  save(): void {
    // Persistence mechanism from DBAs
  }
 
  generateReport(): string {
    // Format requirements from reporting team
  }
}

The accounting department might change pay calculation rules. The DBA might change the database schema. The reporting team might change report formats. Each change risks affecting unrelated functionality.

// After: Each class has one reason to change
class Employee {
  id: string;
  name: string;
  hoursWorked: number;
  hourlyRate: number;
}
 
class PayCalculator {
  calculate(employee: Employee): number {
    return employee.hoursWorked * employee.hourlyRate;
  }
}
 
class EmployeeRepository {
  save(employee: Employee): void {
    // Persistence logic
  }
 
  find(id: string): Employee {
    // Retrieval logic
  }
}
 
class EmployeeReportGenerator {
  generate(employee: Employee): string {
    // Reporting format
  }
}

Common Mistakes

Interpreting “one thing” too literally - SRP isn’t about functions that have one line of code. It’s about cohesive responsibilities that change together.

Creating too many tiny classes - Over-applying SRP can lead to a proliferation of classes that obscures the overall design. Use judgment.

Ignoring the “reason to change” test - Ask: who would request this change? If the answer is “two different teams,” consider separation.

Coupling through constructors - Even well-separated classes can become coupled if they all require each other in their constructors. Use dependency injection.

References

  • Martin, R. C. (2008). Clean Code: A Handbook of Agile Software Craftsmanship. Chapter 10.
  • Martin, R. C. (2017). Clean Architecture: A Craftsman’s Guide to Software Structure and Design. Chapter 7.