CMD Guide
HomeOO & Low-Level DesignSOLID Principles

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

TermWhat it governsUnit of measureDirection you want
SRPReasons a module can change (its actors/stakeholders)count of distinct reasons to changeexactly 1
CohesionHow related a class's members are to each otherfraction of methods that share fields (LCOM)high
CouplingHow many other modules this one depends oncount of distinct collaborators / fan-outlow
SoCWhether each concern lives in one place system-widenumber of modules a concern is smeared across1 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:

DialReading (before)Why
Reasons to change (SRP)3GST rate change (Finance), DB schema change (DBA), email template change (Marketing) — three actors can each force an edit to this one file.
CohesionLowsubtotal 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.
SoCViolatedThree 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:

DialAfterWhat changed mechanically
Reasons to changePricingService=1, OrderProcessor=1 (the orchestration only)A GST change now edits one class; a schema change edits the OrderRepository impl, never pricing.
CohesionHigh per classEvery method in PricingService works on item prices — members are about one thing.
CouplingLow & abstractOrderProcessor depends on interfaces, not jdbc/smtp concretes. You can unit-test pricing with zero infrastructure.
SoCAchievedPricing / 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.

diagram
diagram

Pitfalls

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 hereWhat 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


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.

🎨 Explain it visually

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.
🤔 Walk me through it (interactive)

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.
🧪 Quiz me & fix my gaps

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.
🧠 Make it stick

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.

📝 My notes