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
- Identify class responsibilities explicitly โ what CAN change?
- Extract at "change-rate boundaries" โ things that change differently belong in different classes
- Keep orchestration thin โ the orchestrator knows everything but does nothing complex
- 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
Discussion
Share your thoughts, ask questions, or help others.
Loading comments...