2.2.4 · D5Design Principles

Question bank — SOLID — Single Responsibility Principle

2,455 words11 min readBack to topic

Before you start — the four words you need

Everything below leans on four ideas. Here they are in plain language so this page stands on its own; the full build lives in the parent SRP topic note.

Picture the word "actor" first

The word actor stays slippery until you see it. Look at the org chart below. Each person on the left is an actor — a single source of requirements, a role who can walk up and say "change this". The arrows are pull-on-class lines: an arrow from an actor to a class means "this person can request changes to that class".

Figure — SOLID — Single Responsibility Principle

Why two actors on one class is dangerous — a picture

Look at the next figure. One actor pulling on one class is safe (left, teal): a change request has nowhere else to land. Two actors pulling on the same class collide (right, plum): a change requested by Actor 1 travels through shared code and can knock over behaviour Actor 2 relied on — even though nobody asked Actor 2's feature to change.

Figure — SOLID — Single Responsibility Principle

Putting a number on the collision — deriving

The parent note claims the risk formula . Let us build it from zero so it is never just asserted. First, the two ingredients:

Now build — the chance that some unintended actor is hit by one change — step by step. Watch the figure after it grow the exponent as grows.

Figure — SOLID — Single Responsibility Principle

We use and again in the Edge cases section below — keep this curve in mind.


True or false — justify

A class with 10 methods automatically violates SRP.
False. SRP counts reasons to change (actors), not methods. Ten methods that all serve one actor (say, Accounting) is one responsibility, so it is SRP-clean.
A class with exactly one public method always satisfies SRP.
False. That one method can still be pulled by two actors — e.g. generateReport() whose format Accounting owns but whose data-source the DBA owns. One method, two reasons to change.
"Single Responsibility" and "do only one thing" mean the same.
False. "One thing" is about operations; SRP is about one actor / one reason to change. Conflating them causes over-splitting into anaemic one-method classes.
Applying DRY (removing duplicate code) always improves an SRP design.
False. Merging duplicate logic used by two different actors into one shared helper re-couples them — a change for one actor now risks the other. See DRY Principle: DRY across actors fights SRP.
Two classes with identical code can both be correct under SRP.
True. If the duplicate lines are owned by different actors who will evolve them separately, keeping them apart is exactly right — the similarity is coincidental, not a shared reason to change.
A passive data structure like EmployeeData can never violate SRP.
False. It has one reason to change — the data schema. But if you bolt validation policy onto it (a business rule owned by another actor), it now serves two actors and violates SRP.
Adding a Facade over three split classes re-introduces the SRP violation.
False. A Facade only delegates; it holds no policy of its own. Its single reason to change is "the set of operations clients want bundled", so it stays SRP-clean while restoring one entry point.
SRP guarantees low coupling between the classes you create.
Mostly true, with care. SRP raises cohesion (each class has one reason) and lowers coupling across actors; but a careless shared mutable object between the split classes can sneak coupling back in.
A God Object is a class that is simply too large.
False. Size is a symptom, not the definition. A God Object is a class that many unrelated actors all depend on and modify — it violates SRP by serving too many reasons to change.

Spot the error

"I split Employee into PayCalculator and HourReporter, and both call the shared regularHours() helper — perfect SRP."
The shared helper re-couples Accounting and HR: an overtime tweak for Accounting silently changes HR's report. The overtime rule must live in the actor's own class (or be duplicated deliberately), not in one shared helper.
"To be safe I made every method its own class — maximum single responsibility."
Over-splitting. This creates a maze of tiny classes and causes Shotgun Surgery — one logical change now edits many files. SRP asks for one class per actor, not one class per method.
"OrderProcessor both validates the order and saves it to the DB. Same feature, so it's one responsibility."
Two actors: the business/domain owner defines validation rules, the DBA owns persistence. "Same feature" is a UI-level illusion; the reasons to change differ, so it violates SRP.
"My Logger is used by all 40 classes, so it's high cohesion."
Being used widely is fine; the trap is if those 40 callers each demand different logging behaviour, making 40 actors pull on Logger. Wide use ≠ many reasons to change — check who requests changes, not who calls.
"I put calculateTax() and sendEmail() in the same Checkout class because both happen during checkout."
Temporal coupling ("happen together") is not a shared reason to change. Tax rules and email/notification logic answer to different actors and evolve independently — split them.

Why questions

Why does SRP phrase itself as "reason to change" instead of "one job"?
Because "job" is vague and subjective, whereas a reason to change is concrete: it traces to a specific actor who files change requests. It turns a fuzzy heuristic into an org-chart question.
Why can SRP violations cause bugs even when the code is "correct"?
Two actors sharing code means a change requested by one silently alters behaviour the other relies on. The code compiles and runs; it just does the wrong thing for the second actor.
Why does splitting by actor multiply the number of classes, and why is that acceptable?
Each actor gets its own class, so more actors ⇒ more classes. It's acceptable because each is small, single-reason, and independently changeable; a Facade restores one entry point for callers.
Why does SRP make Open/Closed easier to follow afterwards?
Once responsibilities are separated, you can add a new behaviour by adding a new class rather than editing an existing one — extension without modification, which is exactly Open/Closed.
Why does SRP recommend talking through abstractions (Dependency Inversion Principle)?
Separated responsibilities still need to collaborate. Depending on interfaces instead of concrete classes lets one actor's class change internally without forcing the others to recompile or adapt.
Why is aligning modules to the "org chart of stakeholders" the deep justification?
Software's real purpose is to be easy to change, and change requests originate from organisational roles. Matching module boundaries to those roles means each request touches exactly one module.

The over-split vs under-split continuum — a picture

Both extremes are wrong, and the quadrant below (referenced by several edge cases) shows why. The horizontal axis is coupling (how tangled classes are across actors); the vertical axis is cohesion (how single-reason each class is). SRP aims you at the top-left sweet spot: high cohesion, low coupling.

Figure — SOLID — Single Responsibility Principle
  • Under-split (bottom-right): one class serves many actors → high coupling, low cohesion → the God Object corner.
  • Over-split (top-right): one class per method → each is trivially cohesive but they all change together → high coupling again → Shotgun Surgery.
  • SRP sweet spot (top-left): one class per actor → high cohesion, low coupling.

Edge cases

What does SRP say about a class serving exactly one actor with 20 methods?
It is SRP-clean. One reason to change means zero cross-actor risk — this is the degenerate "safe" case (, so on the risk curve above).
What does the risk curve say when , regardless of ?
. If actors truly share nothing that can break, extra actors add no cross-actor risk — but that pristine independence is rare, which is why SRP still separates them.
What does the risk curve say when and ?
. Total coupling means a change certainly breaks the other actor — the maximally dangerous case that SRP exists to eliminate.
Is a utility class of pure stateless functions (e.g. Math) an SRP violation?
Generally no — pure functions with no shared mutable state have no actor-specific policy; they change only if the maths itself changes. The risk arises only if callers start demanding conflicting behaviours from it.
Two actors happen to want identical behaviour today. Split or share?
Split (or duplicate deliberately) if their reasons to change are independent — today's coincidence will diverge. Share only if they genuinely represent one reason to change forever.
Can a single function (not a class) violate SRP?
Yes. A function that both computes a value and writes it to disk answers to two actors (domain logic vs. persistence). SRP applies at method/function granularity too, not just classes.
What if an actor cannot be identified for a piece of code — no one seems to "own" it?
That's a design smell: unowned code has no clear reason to change and often becomes a dumping ground (a proto-God Object). Assign it to the actor whose requirements it truly serves, or delete it.
If splitting a class would create two classes that always change together, should you split?
No. "Always change together" means one shared reason to change — that's high cohesion (top-left of the quadrant), so they belong in one class. Splitting here pushes you into the over-split / Shotgun Surgery corner.

Connections

  • SOLID — Single Responsibility Principle — parent topic; full build of every term used here.
  • Cohesion and Coupling — the vocabulary behind the quadrant diagram above.
  • God Object (anti-pattern) — the under-split corner of the quadrant.
  • Shotgun Surgery — the over-split corner of the quadrant.
  • DRY Principle — the sharpest trap: DRY across actors re-couples them.
  • Facade Pattern — tames the extra classes SRP creates.