Single Responsibility Principle
Imagine a Swiss Army knife that's also your house key, your car key, and your work ID badge. Handy, until you lose it — now you're locked out of everything at once. Software classes can become the same kind of single point of failure when they try to do too much. The Single Responsibility Principle (SRP) — the "S" in SOLID — says: a class should have one reason to change, and only one.
💡 The Bottom Line
Give every class exactly one job. If you can only describe what a class does by using the word "and," it's doing too much — split it. A class with one job is easy to name, easy to test, and safe to change without breaking something unrelated.
What "one reason to change" actually means
SRP isn't about classes being short — a class can be long and still have a single responsibility. It's about who asks for changes. If your accounting team's request and your IT team's request could both land as edits to the same class, that class is serving two masters, and a change for one risks breaking the other.
// Before: one class, three unrelated reasons to change
class Invoice {
calculateTotal() { /* pricing logic — changes when the business team
updates pricing rules */ }
printInvoice() { /* formatting logic — changes when marketing wants
a new invoice layout */ }
saveToDatabase() { /* persistence logic — changes when the DB team
migrates to a new schema or engine */ }
}
Three teams, three reasons to touch this one file. A pricing tweak could accidentally break how invoices are saved, just because they live in the same class.
🙋 There Are No Dumb Questions
Q: Doesn't splitting one class into three just mean more files to manage?
A: Yes — and that's a fair trade. More small, focused files are easier to find, name, and
test than one file doing three jobs. The cost is a bit of navigation; the benefit is that a
pricing bug can no longer break invoice storage.
Splitting it apart
Each responsibility becomes its own class, with its own single reason to change:
class InvoiceCalculator {
calculateTotal(invoice) { /* pricing logic only */ }
}
class InvoicePrinter {
print(invoice) { /* formatting logic only */ }
}
class InvoiceRepository {
save(invoice) { /* persistence logic only */ }
}
Now a change to how invoices are printed can't accidentally break how totals are calculated or how invoices are stored — each class only knows about its own job.
⚠️ Watch Out
Don't take SRP to the extreme of one-method-per-class "for purity's sake." SRP is about grouping things that change for the same reason, together — not about minimizing line counts. A class with five methods that all change together for the same business reason still has a single responsibility.
Why this matters beyond tidiness
Classes with a single responsibility are easier to:
- Name —
InvoicePrinteris obvious; a class doing pricing, printing, and saving has no good name, so it ends up calledInvoiceManagerorInvoiceHelper— a sign it's really several classes wearing a trenchcoat. - Test — you can test pricing logic without spinning up a database or a printer.
- Reuse —
InvoiceCalculatorcan be reused somewhere that never touches printing at all.
🔧 Try It Yourself
Think of a class or file you've written (or seen) that has "Manager," "Helper," or "Util" in its name. List out everything it does. If that list needs the word "and" more than once, sketch how you'd split it into two or three focused classes instead — what would each one be called, and what's the single job of each?
✅ Quick Check
A class named UserManager validates user input, saves users to a database, and
sends welcome emails. According to SRP, what's the main problem?
Next up: what happens when you need to add new behavior to a class without risking the behavior that already works? That's the Open/Closed Principle.