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 inInventoryService, 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:
- How many reasons does this class have to change?
- Can I describe what this class does in one sentence?
- 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:
-
Identify responsibilities before coding
// Ask: What is this class responsible for? public class UserService { // Responsibility: User management } -
Use composition over doing everything yourself
public class UserService { private EmailService emailService; // Delegate email responsibility private UserRepository repository; // Delegate persistence responsibility } -
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
-
Extract methods to separate classes
- If a method doesn’t belong, extract it to a new class
❌ DON’T:
-
Don’t create God classes
- Classes that do everything
- Classes with 50+ methods
-
Don’t mix concerns
- Business logic + Infrastructure
- Data access + Business logic
-
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:
- Identify all responsibilities
- Extract each responsibility into a separate class
- Use composition to coordinate between classes
- Update tests
- 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:
- Review your codebase for SRP violations
- Identify classes with multiple responsibilities
- Refactor incrementally
- Practice explaining SRP with examples
Next: Open/Closed Principle