Skip to content

Single Responsibility Principle (SRP)

When to Use

When designing classes, modules, or functions -- especially when you notice code becoming harder to test, change, or understand.

Decision: What Counts as a Single Responsibility?

If your class... It likely has... Refactor toward...
Changes for multiple business reasons (reporting AND calculation) Multiple responsibilities Separate classes per concern
Has methods unrelated to each other Lack of cohesion Extract groups into their own classes
Mixes persistence, business logic, and presentation Layer violations Layer separation (repository, service, controller)
Has "Manager", "Handler", or "Util" in the name Vague responsibility Specific, focused classes

Pattern: SRP in Action

Violation (PHP):

class UserAccount {
  public function authenticate($username, $password) { /* auth logic */ }
  public function updateProfile($data) { /* update DB */ }
  public function sendWelcomeEmail() { /* email logic */ }
  public function generateReport() { /* PDF generation */ }
}
// Changes for auth, DB schema, email provider, report format

SRP-Compliant (PHP):

class Authenticator {
  public function authenticate($username, $password) { /* auth only */ }
}
class UserRepository {
  public function update(User $user) { /* DB only */ }
}
class EmailService {
  public function sendWelcomeEmail(User $user) { /* email only */ }
}
class UserReportGenerator {
  public function generate(User $user): Report { /* reporting only */ }
}

TypeScript Example:

// Violation: mixed concerns
class OrderProcessor {
  processOrder(order: Order) { /* business logic */ }
  saveToDatabase(order: Order) { /* persistence */ }
  sendConfirmationEmail(order: Order) { /* notification */ }
}

// SRP-Compliant: separated concerns
class OrderService {
  processOrder(order: Order): ProcessedOrder { /* business logic */ }
}
class OrderRepository {
  save(order: Order): void { /* persistence */ }
}
class NotificationService {
  sendOrderConfirmation(order: Order): void { /* notification */ }
}

The "One Reason to Change" Test

A class should have one reason to change. If you can describe a class's purpose with "and" or "or", it has multiple responsibilities.

Test: "This class changes when..." - "...the authentication method changes and the email provider changes" -- Multiple responsibilities - "...the authentication method changes" -- Single responsibility

Common Mistakes

  • "One method = one class" -- SRP is about cohesion, not granularity. Related methods belong together
  • Creating anemic domain models -- Business logic IS a responsibility; don't extract it if it belongs to the entity
  • Splitting too early -- Wait until you have a real reason to change before splitting
  • Confusing SRP with "one public method" -- Private helpers supporting one responsibility are fine
  • God objects with "Manager" suffix -- UserManager doing auth, CRUD, reporting, notifications violates SRP. Why: impossible to test in isolation, changes ripple everywhere, unclear ownership