Question 13 · Section 18

How is Single Responsibility Related to Cohesion

LCOM analyzes how many methods share common fields. Computed by static analyzers (SonarQube, jdepend):

Language versions: English Russian Ukrainian

🟢 Junior Level

Cohesion is a measure of how closely the methods and fields of a class are related to each other. SRP and cohesion are closely linked: when a class has a single responsibility, its elements naturally work toward one goal — that’s high cohesion.

Simple analogy: Imagine a team. If everyone works on one task (high cohesion) — everything is great. If everyone does their own thing (low cohesion) — chaos.

High cohesion example:

// High cohesion: all methods work with orders
public class OrderService {
    public Order createOrder() { /* ... */ }
    public void cancelOrder(Order order) { /* ... */ }
    public BigDecimal calculateTotal(Order order) { /* ... */ }
}

Low cohesion example:

// Low cohesion: methods do completely different things
public class UserManager {
    public void sendEmail() { /* email */ }
    public void calculateSalary() { /* salary */ }
    public void generateReport() { /* reports */ }
    public void saveToDatabase() { /* DB */ }
}

When to strive for high cohesion:

  • When designing new classes where responsibility hasn’t settled yet
  • When refactoring “bloated” classes
  • Don’t chase perfect cohesion in prototypes and MVPs — speed matters more there

🟡 Middle Level

How It Works

Cohesion — an internal characteristic of a class: how well its parts are “glued together” for a common purpose. SRP — an external rule indicating where to cut the system.

Connection: Following SRP generally leads to high cohesion. A class with a single responsibility naturally has methods that work on one task. But there are exceptions — for example, an orchestrator class may coordinate several subsystems while staying within a single responsibility.

Myers’ Cohesion Levels (classification by Glenford Myers, author of “Reliable Software Through Composite Design”)

Level Description Example Quality
Functional Class does one thing StringTokenizer ✅ Ideal
Sequential Methods form a pipeline ImageTransformer ✅ Good
Communicational Methods work on the same data OrderRepository ✅ Acceptable
Procedural Methods execute in a specific order StartupInitializer ⚠️ Tolerable
Temporal Methods execute at the same time ConfigLoader ⚠️ Tolerable
Utility Weak grouping CommonUtils ❌ Bad
Coincidental Random grouping Helper ❌ Terrible

LCOM Metric (Lack of Cohesion in Methods)

LCOM analyzes how many methods share common fields. Computed by static analyzers (SonarQube, jdepend):

  • If 10 methods: 5 work with field A, 5 with field B → LCOM is high → class should be split into 2 classes
  • LCOM = 0: all methods share common fields (high cohesion)
  • LCOM > 1.0: methods form independent groups (low cohesion)

SRP and Coupling Balance

Goal: High Cohesion + Low Coupling

Approach Cohesion Coupling Result
God Object (one class does everything — violates SRP) Low Low ❌ Fragile
Atomic Dust (too tiny classes — hundreds of 5-line classes) High Monstrous ❌ Complex
Balance High Low ✅ Ideal

Common Mistakes

Mistake Solution
Class with “And/Or/Manager” in the name Split by responsibility
LCOM > 1.0 Extract separate classes
Package-by-Layer (grouping by technical layers: all controllers, all services, all repositories) → low cohesion Package-by-Feature (grouping by business features: Order, User, Payment — each package contains its own controller + service + repository)

When NOT to Chase Perfect Cohesion

  • Prototypes/MVPs (speed matters more)
  • Small utilities (up to 200 lines)
  • When balance is broken the other way (atomic dust)

🔴 Senior Level

Internal Implementation: LCOM4 and the Cohesion Graph

LCOM4 (LCOM version 4, improved by Henderson-Sellers) is computed via a dependency graph:

  • Nodes = methods + fields
  • Edges = method uses field
  • If the graph splits into N components → the class should be split into N parts
Methods: [m1, m2, m3, m4, m5]
Fields: [f1, f2, f3, f4]

m1→f1, m2→f1, m3→f2  (Component 1)
m4→f3, m5→f4          (Component 2)

LCOM4 = 2 → split into 2 classes

Architectural Trade-offs

Strict SRP (Functional Cohesion):

  • ✅ Pros: Ideal testability, minimal side effects, easy refactoring
  • ❌ Cons: Class explosion, high coupling between classes, cognitive load

Balanced SRP (Communicational Cohesion):

  • ✅ Pros: Balance of testability and navigability
  • ❌ Cons: Requires mature judgment, gradual degradation risk

Edge Cases

  1. Anemic Domain Model (classes with only getter/setter, no business logic — all computation moved to services): Classes with only getter/setter → LCOM = 0, but that doesn’t mean “good”
    • Solution: DDD (Domain-Driven Design — an approach where business logic lives near the data, not in separate services) — keep logic close to data
  2. Package-by-Layer vs Package-by-Feature:
    • Layer: [Controller, Service, DAO] → low cohesion at the package level
    • Feature: [Order, User, Payment] → high cohesion, SRP at the module level
  3. Transaction Boundaries: One class coordinates a transaction across multiple domains
    • Solution: Orchestrator pattern, but business logic in separate services

Performance

Cache Locality:

High cohesion:
  class OrderProcessor { fields: order, items, total }
  → Data close in memory → L1 cache hit rate >90%

Low cohesion:
  class GodObject { fields: order, email, db, cache, log, ... }
  → Data scattered → L1 cache miss rate >30%

CPU Cache Impact: High cohesion → better spatial locality → +15-30% throughput

Thread Safety

  • High cohesion classes: Usually simpler to synchronize (one lock per class)
  • Low cohesion (God Object): Multiple responsibilities → lock contention → bottleneck

Production Experience

BillingEngine Refactoring (12,000 lines): LCOM4 = 8, 6 fields used by different subsets of methods. Split into:

  • TaxCalculator (Functional cohesion)
  • InvoiceGenerator (Sequential cohesion)
  • PaymentProcessor (Communicational cohesion)
  • BillingOrchestrator (coordinator)

Result: LCOM4 = 1.0 for each, deployment bugs ↓70%.

Monitoring

ArchUnit:

@ArchTest
static void classes_should_have_high_cohesion = classes()
    .should().haveSimpleNameNotContaining("Manager")
    .andShould().haveSimpleNameNotContaining("Utils")
    .andShould().haveLessThanNDependencies(7);

SonarQube: LCOM > 1.0 → Code Smell, Cognitive Complexity > 15

jdepend: Metrics reports → Lack of Cohesion in Methods

Best Practices for Highload

  • Package-by-Feature for modular cohesion
  • LCOM4 < 1.0 as a target metric
  • Functional Cohesion for hot-path classes
  • ArchUnit for automatic checking

🎯 Interview Cheat Sheet

Must know:

  • Cohesion — how well the class’s methods work toward one goal; SRP → high Cohesion
  • Myers’ cohesion levels: Functional (ideal) → Sequential → Communicational → Procedural → Temporal → Utility → Coincidental (terrible)
  • LCOM (Lack of Cohesion in Methods): LCOM = 0 → high cohesion, LCOM > 1.0 → worth splitting
  • LCOM4: if the dependency graph splits into N components → split the class into N parts
  • Goal: High Cohesion + Low Coupling — avoid God Object and Atomic Dust
  • Package-by-Feature gives high cohesion, Package-by-Layer gives low cohesion
  • High cohesion → better spatial locality → +15-30% throughput (CPU cache impact)

Frequent follow-up questions:

  • What level of cohesion to aim for? — Functional for hot-path, Communicational for most classes
  • What does LCOM4 = 2 mean? — Class should be split into 2 independent classes
  • Anemic Domain Model and cohesion? — Getters/setters → LCOM = 0, but that’s not “good” — logic is moved to services
  • What is Atomic Dust? — Classes that are too tiny (hundreds of 5-line classes), high cohesion but monstrous coupling

Red flags (DO NOT say):

  • “Always chase perfect cohesion” (prototypes/MVPs — speed matters more)
  • “LCOM = 0 always means good design” (Anemic Domain Model also gives LCOM = 0)
  • “Package-by-Layer is the best approach” (low cohesion at the package level, Package-by-Feature is better)

Related topics:

  • [[1. What is Single Responsibility principle and how to apply it]]
  • [[14. What happens if a class has multiple reasons to change]]
  • [[21. How to determine if a class has single responsibility]]
  • [[18. How to refactor God Object]]