Question 2 · Section 18

Give an Example of Single Responsibility Principle Violation

SRP violation creates implicit dependencies in the system. Changing one small detail (e.g., date format in logs) can break a critical business process (e.g., order saving), beca...

Language versions: English Russian Ukrainian

🟢 Junior Level

Single Responsibility Principle (SRP) violation occurs when a class handles multiple unrelated things simultaneously. Such a class is often called a God Object — it knows and does too much.

Simple analogy: Imagine a person who works as a doctor, driver, and cook at the same time. They might do everything, but the quality will be low, and if they get sick — everything collapses.

SRP violation example:

// Bad: OrderProcessor does too much
public class OrderProcessor {

    public void processOrder(Order order) {
        // 1. Order validation
        if (order.getAmount().compareTo(BigDecimal.ZERO) <= 0) {
            throw new IllegalArgumentException("Invalid amount");
        }

        // 2. Save to database
        saveToDatabase(order);

        // 3. Send email to client
        sendEmail(order);

        // 4. Logging
        logToSystem(order);
    }

    private void saveToDatabase(Order order) { /* 50 lines of code */ }
    private void sendEmail(Order order) { /* 50 lines of code */ }
    private void logToSystem(Order order) { /* 50 lines of code */ }
}

Why this is bad:

  • If the email sending method changes — you have to modify the class responsible for orders
  • Hard to test: to verify validation, you need to mock the database and email
  • Cannot reuse: you can’t use saveToDatabase separately

How to fix:

// Good: each responsibility in its own class
public class OrderProcessor {
    private final OrderValidator validator;
    private final OrderRepository repository;
    private final EmailService emailService;
    private final Logger logger;

    public void processOrder(Order order) {
        validator.validate(order);
        repository.save(order);
        emailService.sendOrderConfirmation(order);
        logger.log("Order processed: " + order.getId());
    }
}

🟡 Middle Level

Anatomy of SRP Violation

SRP violation creates implicit dependencies in the system. Changing one small detail (e.g., date format in logs) can break a critical business process (e.g., order saving), because all of this lives in one file.

Typical SRP Violation Examples

1. Spring @Service — “Dump”

A common anti-pattern in Spring projects: one service with dozens of repositories.

@Service
public class GeneralService {
    @Autowired private UserRepository userRepo;
    @Autowired private OrderRepository orderRepo;
    @Autowired private ProductRepository productRepo;
    @Autowired private PromoRepository promoRepo;
    @Autowired private AuditRepository auditRepo;
    // ... 15 more repositories

    // Each @Autowired creates a proxy object. 20+ dependencies = 20+ proxies
    // = complex call chain. During testing, each of the 20 needs to be mocked.

    // This method violates SRP: combines logic of 5 different domains
    public void checkout(Long userId, Long cartId) {
        // 200 lines of logic
    }
}

Consequence: Any Git merge causes conflicts, as 10 developers edit this file daily.

2. God Object in Business Logic

public class EmployeeManager {
    // HR responsibility
    public void hireEmployee(Employee emp) { }
    public void fireEmployee(Long id) { }

    // Accounting
    public BigDecimal calculateSalary(Employee emp) { }
    public BigDecimal calculateTax(Employee emp) { }

    // IT infrastructure
    public void createEmailAccount(Employee emp) { }
    public void assignLaptop(Employee emp) { }

    // Reporting
    public void generateDepartmentReport() { }
}

Problem: Different stakeholders (HR, accounting, IT) demand changes in one class.

SRP Violation Diagnostics

Sign What It Means
“And” Rule If the class description contains “and” (“it does X AND Y AND Z”)
Mocks Overload More than 5 mocks in a unit test
Import List Size Import list takes >20 lines
Cyclomatic Complexity Many branches if/else for different scenarios. Cyclomatic Complexity — number of independent execution paths in a method. Counted as number of if/else/switch branches + 1. CC = 10 means 10+ tests for full coverage. Higher = harder to test.
Merge Conflicts Frequent Git conflicts when editing the file

Common Mistakes

  1. Mistake: Creating Utils, Helper, Manager classes Solution: Break by domain areas (PasswordHasher, DateFormatter). In DDD, Manager/Orchestrator are legitimate coordination patterns. The problem is not the name, but that the class mixes business logic with infrastructure.

  2. Mistake: Combining CRUD operations into one service Solution: Separate by business capabilities (OrderCreator, OrderCanceler)

  3. Mistake: Mixing business logic and infrastructure Solution: Separate DB, HTTP, queue operations into distinct layers

Refactoring: from God Object to Orchestrator

Step 1: Identify different responsibilities Step 2: Create separate classes for each Step 3: Use composition for delegation Step 4: Original class becomes a coordinator (Orchestrator)


🔴 Senior Level

Internal Implementation: Why God Object is “Poison” for a Project

SRP violation creates fundamental problems at the architectural level:

1. Fragility

When we replace one dependency (e.g., Splunk → ELK), we have to retest the entire class logic, because all methods live in one file and may share common fields or state.

// Fragile God Object
public class OrderProcessor {
    private final HttpClient httpClient;  // Used for email AND logging
    private final Connection dbConnection; // Used for orders AND audit

    public void process(Order order) {
        // If httpClient breaks — both email and logging fail
        // Impossible to update logging without risking breaking email
    }
}

2. Immobility

A God Object cannot be reused without dragging all its dependencies along. This violates the Mobility principle — the ability of components to work in different contexts.

3. Low Testability

// Test for God Object — nightmare
@Test
void testProcessOrder() {
    // Need to mock: DB, email, logging, analytics, inventory...
    when(mockDb.save(any())).thenReturn(true);
    when(mockEmail.send(any())).thenReturn(true);
    when(mockLog.log(any())).thenReturn(true);
    when(mockAnalytics.track(any())).thenReturn(true);
    when(mockInventory.reserve(any())).thenReturn(true);
    // 20 lines of mocks for 5 lines of real logic
}

Architectural Trade-offs

Strict decomposition:

  • ✅ Pros: Ideal testability, independent deploys, clear boundaries
  • ❌ Cons: Many classes, navigation complexity, communication overhead

Moderate decomposition:

  • ✅ Pros: Balance of readability and flexibility
  • ❌ Cons: Requires mature judgment, gradual degradation

Edge Cases

  1. Transaction Boundaries: When one transaction spans multiple domains
    • Solution: Use @Transactional at the orchestrator level, but business logic in separate services
  2. CQRS Pattern: Separation of Command and Query responsibilities
    • Example: OrderCommandService (create/delete) vs OrderQueryService (read)
  3. Event-Driven Architecture: Separation through events
    • Example: OrderCreatedEvent → separate handlers for Email, Analytics, Inventory

Performance

  • Method call overhead: Delegation adds method calls. JIT performs inlining, overhead ~0
  • Memory: More objects → more references. GC handles this efficiently (generational GC)
  • ClassLoader: More classes → more metadata in Metaspace (a few KB per class)

Production Experience

Real story from an enterprise project:

In a banking project, there was a PaymentProcessor with 5000+ lines and 40 dependencies. Problems:

  • Any change took 3-4 days (analysis + tests)
  • 30% of all production bugs were due to side effects
  • Merge conflicts every day
  • New developers took 2-3 months to onboard

Solution: Gradual refactoring over 4 sprints:

  1. Extracted PaymentValidator, PaymentRepository, NotificationService
  2. Created FraudDetectionService, ComplianceChecker
  3. PaymentOrchestrator coordinates calls
  4. Event-driven communication via Spring Events

Result:

  • Bug count dropped by 70%
  • Code review time reduced from 2 hours to 30 minutes
  • New features added in 1-2 days instead of a week

Monitoring and Diagnostics

Static analysis:

// ArchUnit test for SRP verification
@ArchTest
static void services_should_have_single_responsibility = classes()
    .that().resideInAPackage("..service..")
    .should().haveSimpleNameNotContaining("Manager")
    .andShould().haveSimpleNameNotContaining("Utils")
    .andShould().haveLessThanNDependencies(7);

Metrics to track:

  • Lines per class (< 300)
  • Number of methods (< 10)
  • Cyclomatic complexity (< 15)
  • Number of dependencies (< 7)
  • Number of mocks in tests (< 5)

Best Practices for Highload

  • Domain-Driven Design: Aggregate Root has one responsibility within the Aggregate
  • Package-by-Feature: Group by business capabilities, not technical layers
  • Hexagonal Architecture: Separation of domain, application and infrastructure layers
  • Event Sourcing: State separation through event model

Relationship with Other Patterns

  • SRP + Decorator: Adding responsibility without violating SRP
  • SRP + Chain of Responsibility: Each handler — one responsibility
  • SRP + Strategy: Each strategy — one responsibility

Summary for Senior

  • SRP violation turns the system into a “house of cards” — changing one detail breaks everything
  • Look for God Object signs: huge methods, dozens of dependencies, frequent merge conflicts
  • Use decomposition by business meaning, not by technical functions
  • Remember: SRP makes code clean, not small — balance between cohesion and file count
  • ArchUnit is your friend for automatic verification of architectural boundaries
  • SRP is an investment in maintainability, not “bureaucracy”

🎯 Interview Cheat Sheet

Must know:

  • God Object — a class that knows and does too much, violates SRP
  • “And” Rule: if class description contains “and” — SRP is violated
  • Spring @Service with 20+ repositories — a typical God Object in enterprise
  • Refactoring: identify responsibilities → create separate classes → delegation
  • SRP violation creates fragility, immobility, and low testability
  • Characterization Tests — first step of refactoring legacy without tests
  • Event-driven architecture helps separate responsibilities through events

Common follow-up questions:

  • How to diagnose a God Object? — >300 lines, >10 methods, >7 dependencies, >5 mocks in tests
  • What is an Orchestrator? — A coordinator that delegates work to separate services, contains no business logic
  • How to refactor without tests? — Characterization Tests / Golden Master: capture current behavior, then split
  • CQRS and SRP? — Separation of Command and Query is an example of SRP at the service level

Red flags (DO NOT say):

  • “Manager is a normal class name” (most often it’s a God Object)
  • “If the code works — SRP is not needed” (works today, but unmaintainable tomorrow)
  • “20 dependencies in a service is normal” (clear sign of SRP violation)

Related topics:

  • [[1. What is Single Responsibility principle and how to apply it]]
  • [[14. What happens if a class has multiple reasons to change]]
  • [[18. How to refactor God Object]]
  • [[22. Which anti-patterns contradict SOLID principles]]