How is Single Responsibility Related to Cohesion
LCOM analyzes how many methods share common fields. Computed by static analyzers (SonarQube, jdepend):
🟢 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
- 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
- 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
- Layer:
- 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]]