SOLID principles
- Single responsibility (S): a class should have one reason to change. An
InvoiceServiceshouldn't also send emails and format PDFs. - Open/closed (O): add behaviour by adding code, not by editing working code. A new payment method is a new class that implements
PaymentGateway. - Liskov substitution (L): a subclass must work wherever its parent is expected. If
Square extends RectanglebreakssetWidth, the hierarchy is wrong. - Interface segregation (I): prefer several small interfaces to one large one, so classes don't implement methods they don't need.
- Dependency inversion (D): depend on abstractions and let something else (such as Spring) supply the implementation. Constructor injection does exactly this.
SOLID isn't a checklist to apply everywhere; it's a set of questions to ask when code becomes hard to change.
Example
// Dependency inversion + open/closed: add gateways without touching checkout
public interface PaymentGateway {
String charge(long paise, String orderId);
}
public class RazorpayGateway implements PaymentGateway {
public String charge(long paise, String orderId) { /* call Razorpay */ return "pay_1"; }
}
public class UpiIntentGateway implements PaymentGateway { // new behaviour = new class
public String charge(long paise, String orderId) { /* UPI intent flow */ return "upi_1"; }
}
interface ReceiptSender { // small, focused interface
void send(Order order, String paymentId);
}
public class CheckoutService { // one responsibility
private final PaymentGateway gateway; // depends on the abstraction
private final ReceiptSender receipts;
public CheckoutService(PaymentGateway gateway, ReceiptSender receipts) {
this.gateway = gateway;
this.receipts = receipts;
}
public void checkout(Order order) {
String paymentId = gateway.charge(order.totalPaise(), order.id());
receipts.send(order, paymentId); // emailing lives elsewhere
}
}Common mistake
Creating an interface for every class "for SOLID". Abstractions have a cost; add them where variation is real.
Under the hood
Over-applying SOLID produces interfaces with a single implementation and layers that only pass calls along. A useful rule: introduce an abstraction when there is (or will clearly be) a second implementation, or when you need to swap it in tests. Liskov violations often show up as instanceof checks or UnsupportedOperationException in subclasses.
Check yourself
Which principle says a class should have only one reason to change?
How this connects
Where this leads
You've reached the end of this thread. Try a learning path for what's next.
Part of Crack the Java interview.
Was this lesson helpful?
Finished reading? Mark it complete to track your progress.