How to determine if a class has single responsibility
Determining whether a class complies with the Single Responsibility Principle (SRP) is one of the most critical foundational skills in software design.
🟢 Junior Level
Determining whether a class complies with the Single Responsibility Principle (SRP) is one of the most critical foundational skills in software design.
In Robert C. Martin’s authoritative definition:
“A class has a single responsibility if it has one, and only one, reason to change—meaning it is responsible to a single business actor.”
The “AND” Elevator Pitch Test
Try to describe the primary purpose of your class in a single clear sentence:
- If your description naturally requires the words “AND”, “OR”, “ALSO”, or “AS WELL AS”, the class carries multiple responsibilities!
- ❌ Low Cohesion / SRP Violation: “The
OrderManagervalidates the shopping cart AND saves records to Postgres AND sends transactional emails AND renders invoice PDFs.” - 🟢 SRP Compliant: “The
OrderValidatorvalidates item availability and shipping constraints.” - 🟢 SRP Compliant: “The
OrderRepositoryhandles database persistence transactions for the Order entity.”
Code Comparison: Scattered Responsibilities vs. Focused Classes
Violation: 4 Disparate Responsibilities in One Service
public class UserService {
// 1. Responsibility to Security Officer: Password hashing algorithms
public String hashPassword(String rawPassword) { /* ... */ }
// 2. Responsibility to DBA: SQL persistence queries
public void saveUserToPostgres(User user) { /* ... */ }
// 3. Responsibility to Communications/Marketing: SMS dispatch
public void sendWelcomeSms(String phone) { /* ... */ }
// 4. Responsibility to Mobile/Frontend Team: JSON payload serialization
public String mapToJson(User user) { /* ... */ }
}
Refactored: Decomposed into 4 Single-Actor Classes
Separate into focused components: PasswordEncoder, UserRepository, SmsNotificationService, and UserJsonMapper.
🟡 Middle Level
The 5 Diagnostic Tests for Accurate SRP Verification
Senior engineers utilize 5 pragmatic diagnostic heuristics to expose hidden SRP violations:
1. The Actor Test (Stakeholder Analysis)
Ask: “Who in the actual organization can demand modifications to this class?”
- If feature change requests can originate simultaneously from the Chief Financial Officer (tax rates), the Security Architect (hashing standards), and the DevOps Engineer (connection pool parameters), the class violates SRP. A class must answer to exactly one business stakeholder role.
2. The Import Analysis Test
Inspect the top of the Java source file:
import java.sql.Connection; // Data Access Layer
import jakarta.mail.Transport; // SMTP Network Protocol
import com.fasterxml.jackson.databind.*; // JSON Data Serialization
import org.springframework.web.bind.annotation.*; // Web REST Presentation
When a single class imports database drivers, mail clients, serialization engines, and web controller annotations simultaneously, it breaches architectural layer boundaries and violates SRP.
3. The Constructor Dependency Count Test
If a class constructor injects more than 5–7 collaborators:
public OrderService(UserRepo u, OrderRepo o, EmailService e, SmsService s,
PdfService p, TaxCalc t, DiscountCalc d, AuditLog a) // 8 dependencies!
This is an immediate signal that the class is degenerating into a God Object, absorbing work that belongs in dedicated services. A healthy, focused class typically injects 2 to 4 collaborators.
4. The Mocking Burden Test in Unit Tests
Examine unit test setups. If verifying a simple 3-line calculation requires 30 lines of Mockito when().thenReturn() configurations across 6 distinct subsystems, the class is overloaded with extraneous responsibilities.
5. The LCOM4 Metric (Lack of Cohesion of Methods)
Static analysis tools (SonarQube) construct a graph where nodes are methods/fields and edges represent shared variable access.
- If the graph fractures into two or more independent connected components ($\text{LCOM4} \ge 2$), the class mathematically contains multiple disconnected programs and must be split into $N$ separate classes.
🔴 Senior Level
Conway’s Law & Bounded Contexts
Robert C. Martin emphasizes that SRP is fundamentally about organizational team communication (Conway’s Law) rather than arbitrary line counts:
[Billing Squad] [Customer Profile Squad]
│ │
└────────────────►[User.java]◄───────────────┘
(Git Merge Collision Ground!)
If a single Java file is modified by developers from two distinct product squads (e.g., Billing and Identity), the class violates SRP. Inevitably, one team’s changes will inadvertently trigger regressions in the other squad’s feature set.
The Domain-Driven Design (DDD) Remedy:
Partition across Bounded Contexts. Each team maintains its own focused aggregate: BillingAccount for the Billing squad, and UserProfile for the Identity squad, decoupled across clean API boundaries.
Command-Query Separation (CQS) as an Architectural SRP Boundary
Bertrand Meyer’s Command-Query Separation (CQS) and its enterprise evolution CQRS provide natural boundaries for single responsibility:
- Commands (State Mutation): Methods that mutate state (return
void, coordinate database transactions, enforce business validation rules:CreateOrderCommand,CancelSubscriptionCommand). - Queries (Data Retrieval): Methods that only read data (pure, idempotent, free of side effects, optimized for read projections:
GetOrderSummaryQuery).
A class that couples intricate domain transaction workflows with massive multi-table SQL reporting queries violates SRP. Splitting into dedicated Command Services and Query Services keeps the write model lean and the read model fast.
Isolating Cross-Cutting Concerns via Aspect-Oriented Programming (AOP)
A frequent pitfall is polluting pure business domain methods with technical infrastructure concerns (logging, Micrometer metrics, OpenTelemetry distributed tracing, Spring Security authorization):
// ❌ POLLUTED DOMAIN METHOD (Mixes domain logic with 4 infrastructure concerns):
public Order processOrder(OrderRequest req) {
long start = System.currentTimeMillis();
log.info("Processing order for user: {}", req.userId());
securityContext.checkPermission("ORDER_CREATE");
// Actual Domain Logic (2 lines)
Order order = new Order(req);
orderRepository.save(order);
metrics.timer("order.process.time").record(System.currentTimeMillis() - start, TimeUnit.MILLISECONDS);
log.info("Order processed: {}", order.getId());
return order;
}
Senior Engineering Standard: Extract cross-cutting concerns into Spring AOP Aspects, HTTP Interceptors / Filters, or the Decorator Pattern. Business services must remain clean and strictly focused on core domain invariants.
4 Tricky Questions
1. Can a 20-line class violate SRP while a 600-line class adheres to it perfectly?
Answer: Yes, absolutely. Physical Lines of Code (LOC) is not an indicator of responsibility count:
- A 20-line class containing 3 methods—one parsing a CSV row, one issuing an HTTP POST to Slack, and one appending to a local file—answers to three distinct stakeholders (File parsing, External alerting, I/O logging), violating SRP.
- A 600-line class implementing an Abstract Syntax Tree (AST) expression parser or an intricate tax algorithm calculates one well-defined mathematical problem. It answers to a single actor (the Tax Compliance Officer) and exhibits high cohesion ($\text{LCOM4} = 1$). Decomposing it would introduce artificial fragmentation without architectural benefit.
2. Does an Orchestrator / Coordinator class violate SRP if it calls 10 different services?
Answer: No, provided it is implemented correctly.
An Orchestrator possesses its own single, distinct responsibility: Business Workflow Coordination. It knows which steps to execute in which order, but it has zero knowledge of how each step is internally executed. If the orchestrator does not perform raw SQL queries, mathematical rounding, or JSON serialization, but simply delegates (validator.validate() -> paymentGateway.charge() -> notifier.send()), it strictly adheres to SRP.
3. How do you distinguish Accidental Duplication from a true SRP violation during refactoring?
Answer: This represents the classic tension between the DRY (Don’t Repeat Yourself) principle and SRP:
- Accidental Duplication: Two methods in the
UserRegistrationmodule and theShippingDeliverymodule validate telephone numbers using identical regex logic. If account security and carrier logistics could alter their phone formatting rules independently in the future (two separate actors), this is accidental duplication. Merging them into a shared utility forces an unnatural coupling between unrelated business domains. - True SRP Violation: Code changes concurrently for the same business reason driven by the same actor. Only in this scenario should logic be consolidated.
4. Why does the “Anemic Domain Model” deceptively look like SRP compliance while causing God classes?
Answer: The Anemic Domain Model splits entities into pure data carriers (classes with only private fields and getters/setters) and puts all behavior into external service classes.
Superficially, the anemic entity seems to respect SRP because it “only stores data.” However, stripping behavior from entities violates Object-Oriented encapsulation:
- The entity cannot enforce its own business invariants; any caller can mutate fields into invalid states (
order.setStatus(null)). - The surrounding service classes (
OrderService) inevitably swell into massive multi-thousand-line God Objects containing every calculation and mutation imaginable.
Remedy: A Rich Domain Model encapsulates data alongside the operations that protect business invariants, maintaining SRP at the Aggregate boundary.
🎯 Interview Cheat Sheet
30-Second Summary
- Core Definition: One class = One business actor = One reason to change.
- Top 5 Diagnostic Heuristics:
- Elevator Pitch: Can you describe its job without “AND” or “OR”?
- Actor Test: How many distinct company departments request changes?
- Import Analysis: Are database, email, JSON, and web packages mixed in one file?
- Constructor Count: Are there more than 5 injected dependencies?
- LCOM4: Does the method-field graph form a single connected component?
- Cross-Cutting Concerns: Extract logging, security, and metrics into AOP aspects or Decorators.
Common Follow-up Questions
- “How does Conway’s law apply to SRP in microservices?” → Microservice boundaries should map directly to two-pizza engineering teams (bounded contexts) so that a squad owns its service without stepping on other teams’ code.
- “What is the relationship between SRP and the Single Level of Abstraction Principle (SLAP)?” → SLAP dictates that all statements within a single method operate at the same conceptual level; SRP applies this concept to the entire class scope.
Red Flags (DO NOT Say)
- ❌ “A class complies with SRP only if it has a single method.” (A class can have 15 methods as long as they all collaborate toward the same responsibility).
- ❌ “Classes named
Manager,Processor, orHelpernaturally have a single responsibility.” (In production, these naming suffixes almost always mask God Objects). - ❌ “Lines of code is the best way to measure SRP.” (A 20-line class can violate SRP; a 600-line class can be perfectly cohesive).
Related Topics
- What is Single Responsibility Principle and How to Apply It — SRP fundamentals
- How is Single Responsibility related to cohesion — LCOM mathematical metrics
- What happens if a class has multiple reasons to change — The cost of multiple actors
- How to refactor God Object — Decomposing God classes