Single responsibility principle
A class should have just one reason to change.
Every class should own a single slice of the system's behavior, and that slice should be entirely hidden inside it. The wording matters: not "one method", not "one hundred lines" - one reason to change.
Formal statement
Robert Martin's own tightening of the definition is "gather together the things that change for the same reasons, and separate those things that change for different reasons." A reason to change is an actor: a person, a team, or a role in the organization whose requirements would move this code. Two responsibilities means two actors, and the moment those actors' requirements diverge, one of them breaks the other's code by accident. The class itself never has to be small - a class with one reason to change can still be long, and a fifteen-line class that both formats currency and talks to a payment gateway already has two.
Why it exists
The goal is reducing complexity, and complexity is a problem you earn over time rather than one you start with. A 200-line program does not need architecture. Write a dozen decent methods and get on with your life.
The trouble arrives when the program keeps growing. Classes get big enough that you can no longer hold them in your head. Navigation slows to scanning. You start reading an entire file to find one thing, and the number of moving parts overflows whatever mental stack you were using to keep track. That is the moment the code stops being yours and starts being something you negotiate with.
And there is a mechanical cost on top of the cognitive one. A class that does several things has to be edited when any of them changes, and each edit risks breaking the parts you were not thinking about. Two responsibilities in one class means two teams filing changes against the same lines, which is a merge conflict factory with extra steps.
The example
An Employee class holds employee data and, somewhere near the bottom, prints the timesheet
report. Both are perfectly reasonable pieces of code. Together they give the class two
reasons to change: the way the company models employees, and the way finance wants that
report formatted this quarter.
Move the printing into a TimeSheetReport class and the seam is obvious in hindsight. As a
bonus, all the other report-shaped code scattered around the codebase now has somewhere to
go.
A second example
A UserController handling a signup request is a different flavor of the same mistake: it
translates HTTP into a method call, which is its job, and it also carries the full rule set
for what makes a signup valid, which is not. Product changes the password rules and a class
whose actual job is "read a request, write a response" gets a diff.
Pulling the rules into a SignupValidator does more than tidy up UserController - it gives
those rules a caller-independent home. A mobile app's native signup form or a CLI admin tool
can call SignupValidator.validate() directly and get the exact same rules UserController
enforces, instead of duplicating them or reverse-engineering them from HTTP-handling code.
The smell
Two tells, cheaper to spot than reasoning about "reasons to change" from scratch:
- The class name needs "and" to describe it.
UserManagerAndNotifier,OrderProcessorAndLogger. If the honest name has a conjunction in it, the class already told you it has two jobs. - Two different teams keep filing changes against the same class for unrelated reasons.
Payroll edits
Employeeto add a tax field; finance editsEmployeeto change the report format. Neither noticed the other exists, and neither should have to.
Patterns that lean on it
No pattern in the catalog exists purely to enforce single responsibility - it is closer to a precondition most patterns assume already holds than something they produce. But Facade is the closest cousin: it gives a tangle of subsystem responsibilities a single new entry point, which only helps if each subsystem class behind it already has one job to answer for. Extract responsibilities first; a facade in front of a class that still does five things just hides the mess one layer down.
Where it goes wrong
The failure mode is enthusiasm. "One reason to change" is not "one method per class", and taken literally it produces a codebase where every behavior is a class, every class is injected, and understanding one feature means opening eleven files.
Skip the split when:
- The program is small enough to read in one sitting. Complexity you do not have does not need managing.
- The two "responsibilities" always change together. If nobody has ever edited one without the other, they are one responsibility wearing a disguise.
- The extraction would be a class with a single method, no state, and one caller. That is a function, and it was fine as a function.
The useful question is not "does this class do more than one thing?" - almost every class does. It is "have these things ever needed to change independently, and do I expect them to?" If the answer is no, leave it alone and revisit when reality disagrees.
The honest cost, paid every time this principle is applied: every extraction is one more class, one more file, and one more hop for a reader tracing a call from "handle the request" to "here is where the actual rule lives." That cost is worth paying when two reasons to change are already fighting inside one class. It is dead weight when they are not.
Try it yourself: Split Employee into two classes, one that holds employee data and one
that owns the timesheet report, then rewrite the caller that currently calls
employee.printTimeSheetReport() so it goes through the new report class instead.
Check yourself
What exactly does "one responsibility" mean in practice?