SRP vs Coupling Cohesion Separation of Concerns
SRP, coupling, cohesion, and Separation of Concerns are not four interchangeable slogans — they are one cause and three measurable effects: SRP is the design rule ("one axis of change per module"), and when you obey it the responsibilities a class names shrink, which mechanically pulls cohesion up (the methods now all touch the same data), pulls coupling down (each class names fewer collaborators), and produces Separation of Concerns at the system level (each concern lives in exactly one place). The other three are the dials you read off a class to check whether you actually achieved SRP.
The four definitions, sharpened
| Term | What it governs | Unit of measure | Direction you want |
|---|---|---|---|
| SRP | Reasons a module can change (its actors/stakeholders) | count of distinct reasons to change | exactly 1 |
| Cohesion | How related a class's members are to each other | fraction of methods that share fields (LCOM) | high |
| Coupling | How many other modules this one depends on | count of distinct collaborators / fan-out | low |
| SoC | Whether each concern lives in one place system-wide | number of modules a concern is smeared across | 1 per concern |
The key distinction the old page glossed over: SRP is per-module and intent-level; SoC is system-level and structural; cohesion/coupling are the metrics that fall out. Uncle Bob's precise phrasing of SRP is not "do one thing" — it is "a module should have one, and only one, reason to change," i.e. it should answer to a single actor (stakeholder role). That actor framing is what makes the worked example below split the way it does.
One class, traced through all four
Here is a god-class that violates SRP. Watch what each metric reads before the fix.
// BEFORE: one class, three actors
class OrderProcessor {
// --- pricing concern (owner: Finance) ---
double subtotal(List<Item> items) {
double s = 0;
for (Item i : items) s += i.price * i.qty;
return s;
}
double withTax(double subtotal) { return subtotal * 1.18; } // 18% GST
// --- persistence concern (owner: DBA) ---
void save(Order o) {
String sql = "INSERT INTO orders(id,total) VALUES(" + o.id + "," + o.total + ")";
jdbc.execute(sql); // raw JDBC string
}
// --- notification concern (owner: Marketing) ---
void emailReceipt(Order o) {
smtp.send(o.customerEmail, "Receipt #" + o.id, renderHtml(o));
}
Order process(List<Item> items, String email) {
double total = withTax(subtotal(items));
Order o = new Order(nextId(), total, email);
save(o);
emailReceipt(o);
return o;
}
}Now read the dials on OrderProcessor as written:
| Dial | Reading (before) | Why |
|---|---|---|
| Reasons to change (SRP) | 3 | GST rate change (Finance), DB schema change (DBA), email template change (Marketing) — three actors can each force an edit to this one file. |
| Cohesion | Low | subtotal touches no field that save or emailReceipt touches. Three method clusters share nothing — classic low-LCOM smell. |
| Coupling (fan-out) | 3 (jdbc, smtp, HTML renderer) | Pricing logic now transitively depends on the SMTP server and the DB driver. A test of subtotal drags in both. |
| SoC | Violated | Three concerns smeared into one module — change one, risk all three. |
Apply SRP — split by actor, then re-read the dials
// AFTER: split by reason-to-change (actor)
class PricingService { // actor: Finance
double total(List<Item> items) {
double s = 0;
for (Item i : items) s += i.price * i.qty;
return s * 1.18; // GST lives in ONE place now
}
}
interface OrderRepository { // actor: DBA owns the impl
void save(Order o);
}
interface ReceiptSender { // actor: Marketing owns the impl
void send(Order o);
}
class OrderProcessor { // actor: order-flow / use-case owner
private final PricingService pricing;
private final OrderRepository repo;
private final ReceiptSender receipts;
OrderProcessor(PricingService p, OrderRepository r, ReceiptSender s) {
this.pricing = p; this.repo = r; this.receipts = s;
}
Order process(List<Item> items, String email) {
double total = pricing.total(items);
Order o = new Order(nextId(), total, email);
repo.save(o);
receipts.send(o);
return o;
}
}The same trace, re-measured. Note that OrderProcessor still depends on three things — but now through interfaces it does not own, which is the difference between brittle and stable coupling:
| Dial | After | What changed mechanically |
|---|---|---|
| Reasons to change | PricingService=1, OrderProcessor=1 (the orchestration only) | A GST change now edits one class; a schema change edits the OrderRepository impl, never pricing. |
| Cohesion | High per class | Every method in PricingService works on item prices — members are about one thing. |
| Coupling | Low & abstract | OrderProcessor depends on interfaces, not jdbc/smtp concretes. You can unit-test pricing with zero infrastructure. |
| SoC | Achieved | Pricing / persistence / notification each live in exactly one module. |
Why the naive version is not just "ugly" but wrong: in the before-class, a Marketing request to change the receipt HTML forces a recompile and redeploy of the code that computes money. The two have no logical reason to ship together; coupling them means a templating typo can block a pricing hotfix. SRP's "one actor" test predicts exactly this collision before it happens.
Pitfalls
- "One method = one responsibility." SRP is about reasons to change (actors), not verb count. A class with twenty methods that all serve the Finance team has one responsibility; a class with two methods serving two teams has two. Splitting by method count produces anaemic, over-fragmented classes.
- Mistaking low coupling for the goal and over-abstracting. Introducing an interface for every collaborator to "reduce coupling" when there is only ever one implementation adds indirection with no payoff — you trade readable, traceable code for a maze of one-method interfaces. Coupling is a symptom to watch, not a number to minimise blindly.
- High cohesion ≠ SRP automatically. A class can be tightly cohesive (all methods touch the same fields) yet still serve two actors — e.g. a
Userclass whose fields back both authentication logic and profile-rendering logic. Cohesion passes; SRP still fails. Always run the actor test, not just the field-sharing test. - Splitting too early. Applying SRP to a 30-line script before you know which parts change independently invents seams you'll fight later. Wait for the second reason-to-change to actually arrive (the Rule of Three), then split along the observed fault line.
- SoC across the wrong axis. Separating "all the controllers / all the services / all the repos" into layers looks like SoC but smears a single feature across three folders — a feature change touches all three. Concern boundaries should follow change-axes, not technical-layer labels.
- Know the two smells by name. An SRP violation shows up as one of Fowler's two mirror-image smells: divergent change (one class edited for many different reasons — too many actors in one module) and shotgun surgery (one conceptual change forces edits across many classes — one actor's concern smeared everywhere). The cheap metric that catches both: count the files touched per feature/change-request; a healthy module changes for one reason and a feature changes one module.
When to lean on each lens (and when not to)
These are not alternatives you choose between — they are different instruments you reach for at different moments. The senior skill is knowing which one answers the question in front of you.
| Use this lens when… | The signal that points here | What it costs / when to prefer another |
|---|---|---|
| SRP (decide where to split) | You're about to add a feature and aren't sure if it belongs in an existing class. Ask "which actor requested this?" | Costs extra classes + wiring. If no second actor exists yet, prefer leaving it inline (YAGNI) and let cohesion warn you later. |
| Cohesion (decide what stays together) | A class feels "grab-bag"; methods don't share fields. Use it to find the split lines SRP told you to make. | It's a diagnostic, not a directive — high cohesion alone won't catch a two-actor class. Pair with the SRP actor test. |
| Coupling (decide what to depend on) | A change ripples into unrelated modules; a unit test needs a DB or network. Reach for an interface / DIP here. | Each abstraction is indirection + a file to navigate. Prefer a direct concrete call when there's one stable implementation and no test seam needed. |
| SoC (decide module/package layout) | You're laying out folders or services and want a feature change to touch one place. | Over-separation fragments a feature across layers. Prefer feature-/vertical-slice packaging over horizontal layers when changes track features. |
Concrete decision, the GST scenario: Finance asks to change 18% → 12%. With the before-class you'd open the 200-line OrderProcessor, find the magic 1.18 buried in withTax, and redeploy code that also sends emails. Applying the SRP actor test predicted this pain, so you'd already have split PricingService — now the change is a one-line edit in a class no email or DB code can break. Choose to split when a second actor appears; prefer to keep it inline when only one actor has ever touched the code.
Takeaways
- SRP is the cause; cohesion, coupling, and SoC are the effects you measure. Obeying "one actor per module" mechanically raises cohesion, lowers coupling, and yields SoC.
- SRP and SoC differ in scope: SRP is per-module and intent-driven ("one reason to change"); SoC is system-wide and structural ("each concern in one place"). Applying SRP everywhere is how you achieve SoC.
- Run the actor test, not the verb-count test. Count the distinct stakeholders who could demand a change; that number is your responsibility count.
- Don't minimise coupling blindly. Abstractions cost indirection — introduce them where a real second implementation or a test seam exists, not preemptively.
Sources: Robert C. Martin, Clean Architecture (2017), ch. 7 — the "single actor / one reason to change" formulation of SRP; Martin's original "SRP" article and the SOLID papers (objectmentor.com); Edsger Dijkstra, "On the role of scientific thought" (1974) — origin of Separation of Concerns; Yourdon & Constantine, Structured Design (1979) — the coupling/cohesion spectrum and LCOM intuition. Worked OrderProcessor example and metric trace authored for this guide. Re-authored and deepened for this guide — replaced bare compare-and-contrast bullets with a single class traced through all four lenses before and after refactoring.
🤖 Don't fully get this? Learn it with Claude
Stuck on SRP vs Coupling Cohesion Separation of Concerns? Open Claude, copy a block below, and it'll teach you this exact concept — visually and interactively.
Build the mental picture, not memorization.
I just read a lesson on **SRP vs Coupling Cohesion Separation of Concerns** (OO & Low-Level Design) and want to truly understand it. Explain SRP vs Coupling Cohesion Separation of Concerns from first principles using ONE vivid real-world analogy and a visual mental model — draw it as ASCII art or a clear step-by-step diagram — with a concrete example using real numbers. Then ask me one question to check I got the mental picture, and wait for my reply. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
Socratic — adapts to where you're stuck.
Teach me **SRP vs Coupling Cohesion Separation of Concerns** interactively. Ask me ONE guiding question at a time, wait for my answer, and adapt to my confusion — build the idea with me step by step instead of explaining it all at once. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
Active recall exposes what you missed.
Quiz me on **SRP vs Coupling Cohesion Separation of Concerns** with 5 questions, easy to tricky, ONE at a time. Tell me if each answer is right; at the end, explain clearly what I got wrong and why. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
Intuition + hook + flashcards for long-term memory.
Help me remember **SRP vs Coupling Cohesion Separation of Concerns** for the long term: give the one-sentence intuition, a memorable hook/mnemonic, a tiny worked example, and 3 active-recall flashcards (Q -> A). If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.