Design Pattern

Single Responsibility Principle (SRP)

Clean Java-only production-ready implementation.


Core Idea

A class should have only one reason to change.

The Violation

A single OrderService that does everything. Any change to pricing, validation, or notification logic modifies the same class.

Interview Checklist

  1. Identify class responsibilities explicitly โ€” what CAN change?
  2. Extract at "change-rate boundaries" โ€” things that change differently belong in different classes
  3. Keep orchestration thin โ€” the orchestrator knows everything but does nothing complex
  4. Every class becomes independently testable

Common Trap

Over-splitting too early. Start with 2โ€“3 clear seams, then split where change pressure is real.

Where This Appears

  • Parking Lot (#1): extract PricingStrategy, SpotAllocation, PaymentGateway, TicketService
  • Splitwise (#5): extract ExpenseCalculator, BalanceManager, NotificationDispatcher
  • Elevator (#3): extract ElevatorController, RequestDispatcher, DoorManager, SafetyMonitor

The Fix

Extract responsibilities that change at different rates into separate classes, then compose them via orchestration.

// โ”€โ”€โ”€ EXAMPLE 1 โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
// WHAT WE ARE IMPLEMENTING:
// An order processing workflow composed of highly cohesive, single-focus
// subservices.
//
// WHERE THE PRINCIPLE FITS IN:
// The principle separates OrderValidator (validation), PricingService
// (pricing), OrderRepository (DB persistence), and NotificationService
// (notifications) instead of keeping them in a single massive OrderService.
// โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
// --- Model ---
class Order {
    private final String id;
    private final String userId;
    private final double amount;
    private final String couponCode;

    public Order(String id, String userId, double amount, String couponCode) {
        this.id = id;
        this.userId = userId;
        this.amount = amount;
        this.couponCode = couponCode;
    }

    public String getId() { return id; }
    public String getUserId() { return userId; }
    public double getAmount() { return amount; }
    public String getCouponCode() { return couponCode; }
}

// --- Single Responsibility 1: Validation ---
class OrderValidator {
    public void validate(Order order) {
        if (order.getAmount() <= 0) throw new IllegalArgumentException("Amount must be positive");
        if (order.getUserId() == null || order.getUserId().isBlank())
            throw new IllegalArgumentException("User ID required");
        System.out.println("  [Validator] Order " + order.getId() + " validated");
    }
}

// --- Single Responsibility 2: Pricing ---
class PricingService {
    public double applyDiscount(Order order) {
        double discount = 0;
        if (order.getCouponCode() != null && order.getCouponCode().equals("SAVE10")) {
            discount = order.getAmount() * 0.10;
        }
        double finalAmount = order.getAmount() - discount;
        System.out.println("  [Pricing] Discount: " + discount + ", Final: " + finalAmount);
        return finalAmount;
    }
}

// --- Single Responsibility 3: Persistence ---
class OrderRepository {
    public void save(Order order) {
        System.out.println("  [Repository] Order " + order.getId() + " saved to DB");
    }
}

// --- Single Responsibility 4: Notification ---
class NotificationService {
    public void sendConfirmation(String userId, String orderId) {
        System.out.println("  [Notification] Confirmation sent to user " + userId + " for order " + orderId);
    }
}

// --- Orchestrator (thin coordinator) ---
class OrderApplicationService {
    private final OrderValidator validator;
    private final PricingService pricingService;
    private final OrderRepository repository;
    private final NotificationService notificationService;

    public OrderApplicationService(OrderValidator validator, PricingService pricingService,
                                   OrderRepository repository, NotificationService notificationService) {
        this.validator = validator;
        this.pricingService = pricingService;
        this.repository = repository;
        this.notificationService = notificationService;
    }

    public void placeOrder(Order order) {
        validator.validate(order);
        pricingService.applyDiscount(order);
        repository.save(order);
        notificationService.sendConfirmation(order.getUserId(), order.getId());
    }
}

// --- Client ---
public class Main {
    public static void main(String[] args) {
        OrderValidator validator = new OrderValidator();
        PricingService pricing = new PricingService();
        OrderRepository repo = new OrderRepository();
        NotificationService notif = new NotificationService();

        OrderApplicationService app = new OrderApplicationService(validator, pricing, repo, notif);
        Order order = new Order("ORD-001", "user-42", 250.0, "SAVE10");
        app.placeOrder(order);
    }
}

๐Ÿ’ฌReview

Help Us Improve

How helpful was this walkthrough?

Click a star to rate. We actively use this feedback to refine and update our system design content.

Placeholder
Optional but highly appreciated!

Discussion

Share your thoughts, ask questions, or help others.

Loading comments...