Single Responsibility

Overview

The Single Responsibility Principle states that a class should have only one reason to change. A class should have only one job or responsibility.

Definition

A class should have only one reason to change.

A class should have only one job or responsibility. If a class has multiple responsibilities, it becomes harder to maintain and test.

Key Points:

  • One class = One responsibility
  • One reason to change
  • Easier to understand, test, and maintain

Beginner Level

❌ Bad Example (Violates SRP)

// This class does too many things!
public class UserService {
    // Responsibility 1: User management
    public void createUser(User user) { }
    public void updateUser(User user) { }
    public void deleteUser(Long id) { }
    
    // Responsibility 2: Email sending
    public void sendEmail(String to, String subject, String body) { }
    
    // Responsibility 3: Database operations
    public void saveToDatabase(User user) { }
    
    // Responsibility 4: Logging
    public void logUserActivity(String activity) { }
}

Problems:

  • If email service changes, we need to modify UserService
  • If logging changes, we need to modify UserService
  • Hard to test (need to mock email, database, logging)
  • Hard to reuse email functionality elsewhere

✅ Good Example (Follows SRP)

// Each class has ONE responsibility
public class UserService {
    private final UserRepository userRepository;
    private final EmailService emailService;
    private final Logger logger;
    
    public void createUser(User user) {
        userRepository.save(user);
        emailService.sendWelcomeEmail(user.getEmail());
        logger.log("User created: " + user.getId());
    }
}

public class EmailService {
    public void sendWelcomeEmail(String email) { }
    public void sendPasswordResetEmail(String email) { }
}

public class UserRepository {
    public void save(User user) { }
    public User findById(Long id) { }
}

Benefits:

  • ✅ Each class has one responsibility
  • ✅ Easy to test (mock dependencies)
  • ✅ Easy to modify (change email logic without touching UserService)
  • ✅ Reusable (EmailService can be used elsewhere)

Intermediate Level

Worked example

✅ Good example: checkout coordinator

A coordinator that only sequences steps — payment, inventory, shipping, email — each owned by another class.

public class CheckoutService {
    private final PaymentService paymentService;
    private final InventoryService inventoryService;
    private final ShippingService shippingService;
    private final NotificationService notificationService;

    public CheckoutService(
            PaymentService paymentService,
            InventoryService inventoryService,
            ShippingService shippingService,
            NotificationService notificationService) {
        this.paymentService = paymentService;
        this.inventoryService = inventoryService;
        this.shippingService = shippingService;
        this.notificationService = notificationService;
    }

    public void checkout(Order order) {
        paymentService.charge(order);
        inventoryService.reserve(order);
        shippingService.schedule(order);
        notificationService.sendOrderConfirmation(order);
    }
}

Why it’s good:

  • ✅ One reason to change: the checkout sequence
  • ✅ Payment rules live in PaymentService, stock rules in InventoryService, and so on
  • ✅ Easy to test — mock each collaborator independently
  • ✅ Easy to modify one concern without opening the others

❌ Smell to watch for

public class CheckoutService {
    public void checkout(Order order) {
        // talks to Stripe HTTP API
        // updates SQL inventory rows
        // builds a shipping label PDF
        // sends SMTP email
    }
}

That class changes when Stripe changes, when the schema changes, when carriers change, or when email copy changes — four reasons to change, one class.


Advanced Level

Identifying Responsibilities

Questions to ask:

  1. How many reasons does this class have to change?
  2. Can I describe what this class does in one sentence?
  3. Are there multiple concerns mixed together?

Example Analysis:

// Can you describe this in one sentence?
public class OrderProcessor {
    // Payment processing
    public void processPayment(Order order) { }
    
    // Inventory management
    public void updateInventory(Order order) { }
    
    // Shipping
    public void shipOrder(Order order) { }
    
    // Notification
    public void sendConfirmation(Order order) { }
}

Answer: No! This class handles payment, inventory, shipping, and notifications.

Refactored:

public class OrderProcessor {
    private PaymentService paymentService;
    private InventoryService inventoryService;
    private ShippingService shippingService;
    private NotificationService notificationService;
    
    public void processOrder(Order order) {
        paymentService.process(order);
        inventoryService.update(order);
        shippingService.ship(order);
        notificationService.sendConfirmation(order);
    }
}

Cohesion vs Coupling

High Cohesion (Good):

  • Related functionality grouped together
  • Methods work together toward a single goal
  • Easy to understand and maintain

Low Coupling (Good):

  • Classes depend on abstractions, not concrete classes
  • Changes in one class don’t affect others
  • Easy to test and modify

Example:

// High cohesion, low coupling
public class UserService {
    private UserRepository repository; // Depends on interface
    
    public void createUser(User user) {
        validate(user);
        repository.save(user);
    }
    
    private void validate(User user) {
        // Validation logic - cohesive with user creation
    }
}

Common Violations

Violation 1: God Class

A class that knows too much or does too much.

Symptoms:

  • Too many dependencies (10+)
  • Too many methods (50+)
  • Hard to test
  • Hard to understand

Solution: Split into smaller, focused classes.

Violation 2: Mixed Concerns

A class handling multiple unrelated concerns.

Example:

// ❌ Bad: Mixing business logic with infrastructure
public class OrderService {
    public void processOrder(Order order) {
        // Business logic
        calculateTotal(order);
        
        // Infrastructure concern
        sendEmail(order.getCustomerEmail());
        logToFile(order);
        saveToDatabase(order);
    }
}

Solution: Separate business logic from infrastructure concerns.

Violation 3: Feature Envy

A class that uses too much of another class’s data.

Example:

// ❌ Bad: OrderService knows too much about Order internals
public class OrderService {
    public void processOrder(Order order) {
        if (order.getItems().size() > 10) {
            order.setDiscount(0.1);
        }
        order.getCustomer().setTotalOrders(order.getCustomer().getTotalOrders() + 1);
    }
}

Solution: Move logic to the appropriate class.


Best Practices

✅ DO:

  1. Identify responsibilities before coding

    // Ask: What is this class responsible for?
    public class UserService {
        // Responsibility: User management
    }
  2. Use composition over doing everything yourself

    public class UserService {
        private EmailService emailService; // Delegate email responsibility
        private UserRepository repository; // Delegate persistence responsibility
    }
  3. Keep classes focused

    • If you can’t describe the class in one sentence, it might have multiple responsibilities
    • If the class has many dependencies, consider splitting
  4. Extract methods to separate classes

    • If a method doesn’t belong, extract it to a new class

❌ DON’T:

  1. Don’t create God classes

    • Classes that do everything
    • Classes with 50+ methods
  2. Don’t mix concerns

    • Business logic + Infrastructure
    • Data access + Business logic
  3. Don’t be afraid to create small classes

    • Small, focused classes are easier to understand and test

Interview Questions

Beginner Level

Q1: What is Single Responsibility Principle? Answer: A class should have only one reason to change. It should have a single, well-defined responsibility.

Q2: How do you identify if a class violates SRP? Answer:

  • Multiple reasons to change
  • Too many dependencies (indicates multiple concerns)
  • Hard to describe in one sentence
  • Difficult to test
  • Methods that don’t relate to each other

Q3: What are the benefits of SRP? Answer:

  • Easier to understand
  • Easier to test
  • Easier to maintain
  • Easier to reuse
  • Reduced coupling

Intermediate Level

Q4: Can you give an example where SRP is followed? Answer: A CheckoutService that only sequences payment, inventory, shipping, and notification — each concern lives in its own service. The checkout class changes only when the order of steps changes, not when Stripe or SMTP changes.

Q5: How do you refactor a class that violates SRP? Answer:

  1. Identify all responsibilities
  2. Extract each responsibility into a separate class
  3. Use composition to coordinate between classes
  4. Update tests
  5. Refactor incrementally

Advanced Level

Q6: What’s the difference between SRP and cohesion? Answer:

  • SRP: A class should have one reason to change (one responsibility)
  • Cohesion: Related functionality should be grouped together
  • High cohesion often leads to following SRP

Q7: Can a class have multiple methods and still follow SRP? Answer: Yes! As long as all methods serve the same responsibility. For example, UserService can have createUser(), updateUser(), deleteUser() - all serve the responsibility of user management.

Q8: How does SRP relate to other SOLID principles? Answer:

  • SRP helps achieve ISP (smaller classes = smaller interfaces)
  • SRP makes DIP easier (fewer dependencies)
  • SRP supports OCP (easier to extend focused classes)

Summary

Key Points:

  • ✅ One class = One responsibility
  • ✅ One reason to change
  • ✅ Easier to understand, test, and maintain
  • ✅ Use composition to coordinate responsibilities

Quick Reference:

SRP: A class should have only one reason to change

Next Steps:

  1. Review your codebase for SRP violations
  2. Identify classes with multiple responsibilities
  3. Refactor incrementally
  4. Practice explaining SRP with examples

Next: Open/Closed Principle