# Single Responsibility Principle (SRP)

> "A class should have only one reason to change." — Robert C. Martin

## Problem

A class accumulates multiple responsibilities over time. It handles business logic **and** persistence **and** formatting **and** notification. When any one of those concerns changes, you must modify the class — risking breakage in unrelated behavior. Testing becomes difficult because you cannot exercise one concern without dragging in the others.

**Symptoms of SRP violation:**
- A class has methods that serve different actors (e.g., `calculate_pay()` for accounting, `generate_report()` for management)
- Changes to logging break business logic
- You cannot unit-test validation without setting up a database connection
- The class file grows endlessly with unrelated methods

## Solution

Identify the distinct reasons the class might change. Extract each responsibility into its own class or module. Each resulting unit has a single, well-defined purpose and changes only when that purpose evolves.

## When to Use

- A class has grown to serve multiple stakeholders or concerns
- You find yourself modifying one method and worrying about breaking another
- Unit testing requires elaborate setup because concerns are intertwined
- Multiple developers frequently have merge conflicts in the same file

## When to Avoid

- The class is genuinely small and cohesive (splitting would just scatter trivial logic)
- You are writing a short script or throwaway prototype
- The "responsibilities" are tightly coupled by nature and splitting them would introduce unnecessary indirection

## Pseudocode

### Before — mixed responsibilities

```
CLASS User:
    name, email

    METHOD validate():
        IF name IS EMPTY OR email DOES NOT CONTAIN "@":
            RAISE ValidationError

    METHOD save_to_database():
        DB.execute("INSERT INTO users ...")

    METHOD send_welcome_email():
        EmailService.send(to=email, subject="Welcome!")
```

### After — each class owns one concern

```
CLASS User:
    name, email

    METHOD validate():
        IF name IS EMPTY OR email DOES NOT CONTAIN "@":
            RAISE ValidationError

CLASS UserRepository:
    METHOD save(user):
        DB.execute("INSERT INTO users ...")

CLASS UserNotifier:
    METHOD send_welcome(user):
        EmailService.send(to=user.email, subject="Welcome!")
```

Now `User` changes only if validation rules change, `UserRepository` changes only if the storage mechanism changes, and `UserNotifier` changes only if notification requirements change.

## Python

```python
"""
Single Responsibility Principle — Python

BEFORE: Report class generates content AND formats AND emails.
AFTER:  Three focused classes, each with one job.
"""


# === BEFORE: Violating SRP ===

class ReportBefore:
    def __init__(self, title: str, data: list[dict]):
        self.title = title
        self.data = data

    def generate_content(self) -> str:
        lines = [self.title, "=" * len(self.title)]
        for row in self.data:
            lines.append(f"  {row['item']}: ${row['amount']:.2f}")
        total = sum(r["amount"] for r in self.data)
        lines.append(f"  TOTAL: ${total:.2f}")
        return "\n".join(lines)

    def print_report(self) -> None:
        # Formatting concern mixed in
        content = self.generate_content()
        border = "#" * 40
        print(f"{border}\n{content}\n{border}")

    def email_report(self, recipient: str) -> None:
        # Notification concern mixed in
        content = self.generate_content()
        print(f"[EMAIL] Sending to {recipient}:\n{content}")


# === AFTER: Following SRP ===

class Report:
    """Responsible only for holding and generating report content."""

    def __init__(self, title: str, data: list[dict]):
        self.title = title
        self.data = data

    def generate_content(self) -> str:
        lines = [self.title, "=" * len(self.title)]
        for row in self.data:
            lines.append(f"  {row['item']}: ${row['amount']:.2f}")
        total = sum(r["amount"] for r in self.data)
        lines.append(f"  TOTAL: ${total:.2f}")
        return "\n".join(lines)


class ReportPrinter:
    """Responsible only for formatting and printing a report."""

    def print_report(self, report: Report) -> None:
        content = report.generate_content()
        border = "#" * 40
        print(f"{border}\n{content}\n{border}")


class ReportEmailer:
    """Responsible only for emailing a report."""

    def email_report(self, report: Report, recipient: str) -> None:
        content = report.generate_content()
        print(f"[EMAIL] Sending to {recipient}:\n{content}")


# === Demo ===

def main():
    data = [
        {"item": "Widget A", "amount": 29.99},
        {"item": "Widget B", "amount": 14.50},
        {"item": "Widget C", "amount": 45.00},
    ]

    print("--- BEFORE (SRP violated) ---")
    old_report = ReportBefore("Q1 Sales", data)
    old_report.print_report()
    old_report.email_report("boss@example.com")

    print("\n--- AFTER (SRP applied) ---")
    report = Report("Q1 Sales", data)
    printer = ReportPrinter()
    emailer = ReportEmailer()

    printer.print_report(report)
    emailer.email_report(report, "boss@example.com")


if __name__ == "__main__":
    main()
```

## Go

```go
// Single Responsibility Principle — Go
//
// BEFORE: A single UserService struct handles validation, persistence, and notification.
// AFTER:  Three separate structs, each owning one concern.

package main

import (
	"errors"
	"fmt"
	"strings"
)

// ============================================================
// BEFORE: Violating SRP — one struct does everything
// ============================================================

type UserServiceBefore struct{}

func (s *UserServiceBefore) Validate(name, email string) error {
	if name == "" {
		return errors.New("name is required")
	}
	if !strings.Contains(email, "@") {
		return errors.New("invalid email")
	}
	return nil
}

func (s *UserServiceBefore) Save(name, email string) {
	fmt.Printf("  [DB] INSERT INTO users (name, email) VALUES ('%s', '%s')\n", name, email)
}

func (s *UserServiceBefore) SendWelcome(email string) {
	fmt.Printf("  [EMAIL] Welcome message sent to %s\n", email)
}

// ============================================================
// AFTER: Following SRP — separate structs per concern
// ============================================================

// User holds only the data.
type User struct {
	Name  string
	Email string
}

// UserValidator is responsible only for validation.
type UserValidator struct{}

func (v *UserValidator) Validate(u User) error {
	if u.Name == "" {
		return errors.New("name is required")
	}
	if !strings.Contains(u.Email, "@") {
		return errors.New("invalid email")
	}
	fmt.Printf("  [VALID] User '%s' passed validation\n", u.Name)
	return nil
}

// UserRepository is responsible only for persistence.
type UserRepository struct{}

func (r *UserRepository) Save(u User) {
	fmt.Printf("  [DB] INSERT INTO users (name, email) VALUES ('%s', '%s')\n", u.Name, u.Email)
}

// UserNotifier is responsible only for notifications.
type UserNotifier struct{}

func (n *UserNotifier) SendWelcome(u User) {
	fmt.Printf("  [EMAIL] Welcome message sent to %s\n", u.Email)
}

// ============================================================
// Demo
// ============================================================

func main() {
	fmt.Println("--- BEFORE (SRP violated) ---")
	svc := &UserServiceBefore{}
	if err := svc.Validate("Alice", "alice@example.com"); err != nil {
		fmt.Println("  Error:", err)
	} else {
		svc.Save("Alice", "alice@example.com")
		svc.SendWelcome("alice@example.com")
	}

	fmt.Println("\n--- AFTER (SRP applied) ---")
	user := User{Name: "Alice", Email: "alice@example.com"}

	validator := &UserValidator{}
	repo := &UserRepository{}
	notifier := &UserNotifier{}

	if err := validator.Validate(user); err != nil {
		fmt.Println("  Error:", err)
		return
	}
	repo.Save(user)
	notifier.SendWelcome(user)
}
```

## JavaScript

```javascript
// Single Responsibility Principle — JavaScript
//
// BEFORE: OrderProcessor handles price calculation, persistence, AND receipt formatting.
// AFTER:  Split into OrderCalculator, OrderRepository, and ReceiptFormatter.

// ============================================================
// BEFORE: Violating SRP
// ============================================================

class OrderProcessorBefore {
  constructor(items) {
    this.items = items;
  }

  calculateTotal() {
    return this.items.reduce((sum, item) => sum + item.price * item.qty, 0);
  }

  saveToDatabase() {
    const total = this.calculateTotal();
    console.log(`  [DB] Saved order — ${this.items.length} items, total: $${total.toFixed(2)}`);
  }

  printReceipt() {
    console.log("  --- Receipt ---");
    for (const item of this.items) {
      console.log(`  ${item.name} x${item.qty} = $${(item.price * item.qty).toFixed(2)}`);
    }
    console.log(`  TOTAL: $${this.calculateTotal().toFixed(2)}`);
    console.log("  ----------------");
  }
}

// ============================================================
// AFTER: Following SRP
// ============================================================

class Order {
  constructor(items) {
    this.items = items;
  }
}

class OrderCalculator {
  /** Responsible only for price computation. */
  calculateTotal(order) {
    return order.items.reduce((sum, item) => sum + item.price * item.qty, 0);
  }
}

class OrderRepository {
  /** Responsible only for persistence. */
  save(order, total) {
    console.log(`  [DB] Saved order — ${order.items.length} items, total: $${total.toFixed(2)}`);
  }
}

class ReceiptFormatter {
  /** Responsible only for formatting and printing receipts. */
  print(order, total) {
    console.log("  --- Receipt ---");
    for (const item of order.items) {
      console.log(`  ${item.name} x${item.qty} = $${(item.price * item.qty).toFixed(2)}`);
    }
    console.log(`  TOTAL: $${total.toFixed(2)}`);
    console.log("  ----------------");
  }
}

// ============================================================
// Demo
// ============================================================

function main() {
  const items = [
    { name: "Keyboard", price: 49.99, qty: 1 },
    { name: "Mouse",    price: 29.99, qty: 2 },
    { name: "USB Cable", price: 9.99, qty: 3 },
  ];

  console.log("--- BEFORE (SRP violated) ---");
  const oldProcessor = new OrderProcessorBefore(items);
  oldProcessor.saveToDatabase();
  oldProcessor.printReceipt();

  console.log("\n--- AFTER (SRP applied) ---");
  const order      = new Order(items);
  const calculator = new OrderCalculator();
  const repository = new OrderRepository();
  const formatter  = new ReceiptFormatter();

  const total = calculator.calculateTotal(order);
  repository.save(order, total);
  formatter.print(order, total);
}

main();
```

## Related Patterns

- **OCP** — once responsibilities are separated (SRP), each class can be extended independently (OCP).
- **Facade** — after applying SRP, a facade can re-unify the separated classes behind a simple interface.
- **Service Layer** — the service layer applies SRP at the architectural level: orchestration logic is separated from domain logic and persistence.
- **Mediator** — a mediator centralises coordination logic so individual components each have a single responsibility.
