Day 1 — Single Responsibility Principle (SRP)
📋 Topics Covered
- What is SOLID and why interviewers care
- S — Single Responsibility Principle (SRP)
- One class = one reason to change
- Bad vs good example
- How SRP relates to cohesion, coupling, and separation of concerns
- Identifying SRP violations
- Refactoring techniques
- Practical checklist to achieve SRP in real code
📚 My Learning Notes
What is SRP?
A class should have one, and only one, reason to change.
In simple terms: Each class should do ONE thing and do it well.
Why It Matters
- Easier to understand and maintain
- Easier to test
- Changes in one area don't break other areas
- Better code organization
SRP, Cohesion, Coupling, and Separation of Concerns
These concepts are tightly connected and often discussed together in interviews.
1) SRP and Cohesion (aim for high cohesion)
- Cohesion = how strongly related the responsibilities inside a class/module are.
- A class following SRP usually has high cohesion because its methods and fields all support one clear purpose.
- If a class has methods that feel unrelated, cohesion is low and SRP is likely violated.
Quick test: Can you describe the class without using "and"?
- Good: "This class calculates invoice totals."
- Risky: "This class calculates totals and sends emails and writes to DB."
2) SRP and Coupling (aim for low coupling)
- Coupling = how much one class depends on others.
- SRP helps reduce unnecessary coupling by separating responsibilities into focused components.
- When responsibilities are mixed, every change can ripple through multiple dependencies.
Example:
- If
Invoiceboth calculates totals and sends emails, it now depends on pricing rules + email infrastructure. - Splitting those concerns keeps domain logic independent from infrastructure details.
3) SRP and Separation of Concerns (SoC)
- SoC = divide software into distinct concerns (domain, persistence, UI, notifications, etc.).
- SRP is a class-level/application-level way to enforce SoC.
- You can think of SRP as: "SoC applied to objects/components."
Rule of thumb:
- SoC tells you what to separate.
- SRP tells you how small and focused each unit should be.
Common Violations
- God Classes — Classes that do everything
- Manager Classes — Classes with too many responsibilities
- Utility Classes — Classes that mix unrelated functionality
- Multi-layer classes — One class mixing domain logic + database + HTTP/email
💻 Practice Code
❌ Bad Example (SRP Violation)
// This Invoice class has TOO MANY responsibilities
public class Invoice {
private String customer;
private double amount;
// Responsibility 1: Calculate invoice
public double calculateTotal() {
return amount * 1.1; // including 10% tax
}
// Responsibility 2: Save to database
public void saveToDB() {
// database logic here
System.out.println("Saving to DB...");
}
// Responsibility 3: Print invoice
public void printInvoice() {
System.out.println("Printing invoice...");
}
// Responsibility 4: Send email
public void sendEmail() {
System.out.println("Sending email...");
}
}
Problems:
- If DB schema changes, Invoice class must change
- If email format changes, Invoice class must change
- If printing format changes, Invoice class must change
- Hard to test each function independently
✅ Good Example (Following SRP)
// 1. Invoice class — only handles invoice data and calculation
public class Invoice {
private String customer;
private double amount;
public double calculateTotal() {
return amount * 1.1;
}
public String getCustomer() {
return customer;
}
public double getAmount() {
return amount;
}
}
// 2. InvoiceRepository — handles database operations
public class InvoiceRepository {
public void save(Invoice invoice) {
// database logic
System.out.println("Saving to DB: " + invoice.getCustomer());
}
}
// 3. InvoicePrinter — handles printing
public class InvoicePrinter {
public void print(Invoice invoice) {
System.out.println("--- INVOICE ---");
System.out.println("Customer: " + invoice.getCustomer());
System.out.println("Total: $" + invoice.calculateTotal());
}
}
// 4. EmailService — handles email notifications
public class EmailService {
public void sendInvoiceEmail(Invoice invoice) {
System.out.println("Sending email to: " + invoice.getCustomer());
}
}
// Usage
public class Main {
public static void main(String[] args) {
Invoice invoice = new Invoice("John Doe", 100.0);
InvoiceRepository repo = new InvoiceRepository();
repo.save(invoice);
InvoicePrinter printer = new InvoicePrinter();
printer.print(invoice);
EmailService emailService = new EmailService();
emailService.sendInvoiceEmail(invoice);
}
}
Benefits:
- Each class has ONE reason to change
- Easy to test each class independently
- Can replace implementation without affecting others
- Clear separation of concerns
🛠️ How to Achieve SRP in Real Projects
Step-by-step approach
- List reasons to change for a class (business rule, schema change, API contract, formatting, etc.).
- If reasons come from different actors/teams (e.g., product vs DBA vs DevOps), split responsibilities.
- Extract by concern: domain logic, persistence, communication, presentation, validation.
- Keep the domain model focused on business behavior.
- Move side effects (DB, email, file I/O, external APIs) to dedicated services/adapters.
- Add tests per responsibility to lock behavior.
Practical heuristics
- If a class name ends with vague words like
Manager,Helper,Processor, double-check SRP. - If a class imports too many unrelated libraries (DB + mail + HTTP + formatting), it's a smell.
- If one change request regularly modifies the same giant class, split it.
- Prefer composition over bloated inheritance hierarchies.
Avoid over-splitting
- SRP is not "one method per class".
- Keep related behavior together when it changes for the same reason.
- Goal: focused modules, not a class explosion.
🎯 Practice Exercise
Exercise 1: Identify SRP Violations
Look at this User class — what responsibilities does it have?
public class User {
private String name;
private String email;
public void registerUser() { /* ... */ }
public void sendWelcomeEmail() { /* ... */ }
public void logActivity() { /* ... */ }
public void validateEmail() { /* ... */ }
public void saveToDatabase() { /* ... */ }
}
My Answer:
- User registration logic
- Email sending
- Logging
- Validation
- Database operations
Refactored structure:
User— data onlyUserRegistrationService— handles registrationEmailService— sends emailsLogger— logs activitiesEmailValidator— validates emailsUserRepository— database operations
Exercise 2: Real-World Example
Before (Violation):
// My ProductService was doing too much
public class ProductService {
public void createProduct() { }
public void sendInventoryAlert() { }
public void generateReport() { }
public void calculateDiscount() { }
}
After (SRP):
public class ProductService {
public void createProduct() { }
}
public class InventoryAlertService {
public void sendAlert() { }
}
public class ReportGenerator {
public void generate() { }
}
public class DiscountCalculator {
public double calculate() { }
}
📝 Key Takeaways
- One class = one responsibility = one reason to change
- SRP drives high cohesion and supports low coupling
- SRP is a practical form of separation of concerns at class/module level
- If you use "and" when describing what a class does, it may be doing too much
- Ask: "Why would this class need to change?" If multiple unrelated reasons → split it
- SRP makes code easier to test, maintain, and evolve safely
✅ Checklist
- Understand what SRP means
- Understand SRP relation to cohesion, coupling, and SoC
- Identify SRP violations in code
- Know how to refactor to follow SRP
- Practiced with Invoice example
- Applied to real-world code from my projects
🔗 References
- Clean Code by Robert C. Martin
- Design Gurus: Grokking SOLID Design Principles
- Refactoring Guru: SOLID Principles
Status: ✅ Completed on 2026-06-30
Time Spent: 1.5 hours
Next: Day 2 — Open/Closed Principle (OCP)