Single Responsibility Principle
BeginnerA class should have only one reason to change — it should do one thing and do it well.
Overview
SRP is about cohesion: grouping things that change for the same reason, and separating things that change for different reasons. A class that handles HTTP parsing, business logic, database access, and email sending has four reasons to change — it violates SRP. The practical test: "Can I describe this class's responsibility in one sentence without using 'and' or 'or'?" SRP applies at all levels: class, method, and module. Violations manifest as large classes (God classes), methods that do too much, and test files that need to mock dozens of dependencies.
SRP Violation and Refactoring
A UserService that handles registration, email sending, and PDF report generation has three reasons to change. Refactor by extracting each responsibility into its own class.
// ❌ SRP Violation: UserService does too many things
public class UserService {
public void registerUser(String email, String password) {
// 1. Validate
if (!email.contains("@")) throw new IllegalArgumentException("Invalid email");
// 2. Hash password
String hashed = BCrypt.hashpw(password, BCrypt.gensalt());
// 3. Save to database
String sql = "INSERT INTO users (email, password) VALUES (?, ?)";
jdbcTemplate.update(sql, email, hashed);
// 4. Send welcome email
MimeMessage msg = mailSender.createMimeMessage();
// ... email setup ...
mailSender.send(msg);
// 5. Generate PDF welcome packet
Document doc = new Document();
PdfWriter.getInstance(doc, new FileOutputStream("welcome.pdf"));
// ... PDF generation ...
}
// This class changes when: email template changes, PDF library upgrades,
// DB schema changes, validation rules change, or password policy changes
}
// ✅ SRP Refactoring: one class, one responsibility
public class UserRegistrationService { // orchestrates only
private final UserRepository userRepo;
private final PasswordEncoder encoder;
private final EmailService emailService;
private final WelcomeDocService docService;
public UserRegistrationService(UserRepository userRepo, PasswordEncoder encoder,
EmailService emailService, WelcomeDocService docService) {
this.userRepo = userRepo;
this.encoder = encoder;
this.emailService = emailService;
this.docService = docService;
}
public User registerUser(String email, String password) {
User user = User.of(email, encoder.encode(password));
userRepo.save(user);
emailService.sendWelcome(user);
docService.generateWelcomePacket(user);
return user;
}
}
// Each extracted class has one reason to change:
public class EmailService {
public void sendWelcome(User user) { /* only email logic here */ }
}
public class WelcomeDocService {
public void generateWelcomePacket(User user) { /* only PDF logic here */ }
}
public class UserRepository {
public void save(User user) { /* only DB logic here */ }
}SRP at Method Level
SRP applies to methods too. A method that fetches data, transforms it, validates it, and persists it violates SRP. Extract each concern into a named private method or separate class.
// ❌ Method SRP violation
public void processOrder(Order order) {
// Fetch product details
Product p = productRepo.findById(order.getProductId()).orElseThrow();
// Apply discount
double price = p.getPrice();
if (order.hasPromoCode()) price *= 0.9;
// Validate inventory
if (p.getStock() < order.getQuantity()) throw new OutOfStockException();
// Charge payment
paymentGateway.charge(order.getUserId(), price * order.getQuantity());
// Update inventory
p.setStock(p.getStock() - order.getQuantity());
productRepo.save(p);
// Send email
emailService.sendOrderConfirmation(order);
}
// ✅ SRP at method level — each step is named and testable
public void processOrder(Order order) {
Product product = fetchProduct(order);
double price = applyDiscount(product.getPrice(), order);
validateStock(product, order.getQuantity());
chargeCustomer(order, price);
deductStock(product, order.getQuantity());
notifyCustomer(order);
}
private Product fetchProduct(Order order) { return productRepo.findById(order.getProductId()).orElseThrow(); }
private double applyDiscount(double p, Order o) { return o.hasPromoCode() ? p * 0.9 : p; }
private void validateStock(Product p, int qty) { if (p.getStock() < qty) throw new OutOfStockException(); }
private void chargeCustomer(Order o, double p) { paymentGateway.charge(o.getUserId(), p * o.getQuantity()); }
private void deductStock(Product p, int qty) { p.setStock(p.getStock() - qty); productRepo.save(p); }
private void notifyCustomer(Order o) { emailService.sendOrderConfirmation(o); }Key Points to Remember
- 1A class should have one reason to change — one axis of variation, one responsibility.
- 2God classes that do everything are the most common SRP violation.
- 3SRP improves testability — small, focused classes are easier to unit test in isolation.
- 4SRP applies at method level too — methods should do one thing, named as a verb-noun describing that one thing.
- 5SRP and high cohesion are the same idea: group things that change together.
Interview Questions
Sign in to ask AriaWhat does "one reason to change" mean in SRP?
How do you identify an SRP violation in a large codebase?
Can you have too much SRP? What is "over-engineering" in this context?
How does SRP relate to microservices architecture?
Ask Aria about Single Responsibility Principle
Your personal AI tutor — ask anything about this concept
Revision Status
Personal Notes
Sign in to save personal notes for this topic.
Discussion
Sign in to join the discussion.