S: Single Responsibility
One reason to change is a practical way of asking whether responsibilities belong together.
SOLID is probably the most frequently cited principle set in object-oriented design, and one of the most frequently misapplied. These five principles are not a demand to make every class tiny or every design highly abstract. They are lenses for judging responsibility, extension, substitution, interface shape, and dependency direction in systems that need to evolve over time.
In an order-management platform, rules change constantly: shipping workflows expand, payment providers multiply, returns and refunds become more nuanced, and notification channels diversify. SOLID helps you decide whether those changes will fit the existing model or break it open across multiple unrelated parts of the codebase.
SOLID should be read as a connected set of design pressures, not five independent slogans. SRP improves cohesion. OCP gives you safer extension. LSP protects polymorphism from surprise. ISP prevents bloated contracts. DIP keeps high-level policy from being trapped by low-level detail.
The principles become most useful when you apply them to a living business domain. In Java, that often means looking at whether a use case class is too broad, whether payment or notification variation is localized properly, whether abstractions are stable, and whether dependencies point toward business intent or technical detail.
The right outcome is not maximum abstraction. The right outcome is a design that becomes easier to change because responsibilities and boundaries are more honest.
One reason to change is a practical way of asking whether responsibilities belong together.
Good designs absorb foreseeable variation without rewriting stable business flows.
Substitutability, focused interfaces, and dependency direction matter because extension only works when the contracts stay healthy.
package org.javaomnibus.ecommerce.principles;
public final class PlaceOrderUseCase {
private final InventoryReservationService inventoryReservationService;
private final PaymentAuthorizer paymentAuthorizer;
private final ConfirmationNotifier confirmationNotifier;
public PlaceOrderUseCase(
InventoryReservationService inventoryReservationService,
PaymentAuthorizer paymentAuthorizer,
ConfirmationNotifier confirmationNotifier
) {
this.inventoryReservationService = inventoryReservationService;
this.paymentAuthorizer = paymentAuthorizer;
this.confirmationNotifier = confirmationNotifier;
}
public void handle(Order order) {
inventoryReservationService.reserve(order);
paymentAuthorizer.authorize(order);
confirmationNotifier.notify(order);
}
}
interface InventoryReservationService { void reserve(Order order); }
interface PaymentAuthorizer { void authorize(Order order); }
interface ConfirmationNotifier { void notify(Order order); }
record Order(String orderId) {}
SOLID can be overused when teams introduce abstractions before the variation is real. That creates indirection without payoff. The principles should simplify reasoning, not make the design harder to follow.