Topic 1.2
SOLID
In one line
Five principles that keep a codebase changeable: Single Responsibility, Open/Closed, Liskov Substitution, Interface Segregation, Dependency Inversion. For interviews, connect each to the production pain it prevents.
Think of it like this
A well-organised kitchen. Each tool does one job (S), you can add a new gadget without rebuilding the kitchen (O), any brand of kettle works in the kettle spot (L), the toaster doesn't come with buttons for the fridge (I), and appliances plug into a standard socket, not straight into the wiring (D).
Key ideas
- 01
S — Single Responsibility: a class has one reason to change. Pain prevented: a 'God class' where every new rule touches one huge file and breaks everyone.
- 02
O — Open/Closed: open for extension, closed for modification. Add new behaviour without editing existing classes. Pain prevented: regression risk on every change.
- 03
L — Liskov Substitution: subclasses must be usable wherever the base is expected, without changing correctness. Pain prevented: subclass special-casing that quietly breaks callers.
- 04
I — Interface Segregation: don't force clients to depend on methods they don't use. Pain prevented: fat interfaces and forced empty implementations.
- 05
D — Dependency Inversion: depend on abstractions, not concretions. Pain prevented: testing and swapping production details becomes impossible.
- 06
Interview framing: for each letter, name the exact failure it prevents ('without OCP, every new payment type means editing PaymentProcessor and re-testing the world').
Java / Spring map
- →
Spring is Dependency Inversion in action: @Autowired depends on the interface, the container hands you the bean.
- →
OCP in Java: strategy/state pattern classes behind a Map<String, Strategy>; adding a type = adding an entry, not editing a switch.
Code & diagrams
SRP + OCP + DIP in one small example: payment processing.
// S: each class owns exactly one reason to change.
public class OrderFees {
public double calculate(Order o) { return o.amount() * 0.02; }
}
// O + D: an interface, implementations plugged from outside.
public interface PaymentGateway { // DIP: depend on this abstraction
boolean charge(Order o);
}
public class StripeGateway implements PaymentGateway {
public boolean charge(Order o) { /* Stripe SDK call */ return true; }
}
public class PaypalGateway implements PaymentGateway {
public boolean charge(Order o) { /* Paypal SDK call */ return true; }
}
public class Checkout {
private final PaymentGateway gateway; // constructor injection (DIP)
public Checkout(PaymentGateway gateway) { this.gateway = gateway; }
// Adding "UPI gateway" tomorrow? New class only. Checkout never changes → OCP.
public void pay(Order o) { gateway.charge(o); }
}The two letters people struggle to demonstrate: L and I.
// L — VIOLATION: Square "is-a" Rectangle mathematically, not behaviourally.
class Rectangle {
protected int w, h;
void setWidth(int w) { this.w = w; }
void setHeight(int h) { this.h = h; }
int area() { return w * h; }
}
class Square extends Rectangle {
@Override void setWidth(int w) { this.w = this.h = w; } // breaks the
@Override void setHeight(int h) { this.w = this.h = h; } // parent's contract
}
// Caller written against Rectangle:
// r.setWidth(5); r.setHeight(4); assert r.area() == 20; // FAILS for Square
// FIX: no inheritance. Both implement an immutable Shape { int area(); }.
// L — also a violation: a subclass that throws where the base didn't.
class ReadOnlyList<T> extends ArrayList<T> {
@Override public boolean add(T t) { throw new UnsupportedOperationException(); }
}
// I — VIOLATION: one fat interface forces empty implementations.
interface Worker { void code(); void attendStandup(); void chargeBattery(); }
class Robot implements Worker {
public void attendStandup() { /* meaningless */ } // smell
public void code() {}
public void chargeBattery() {}
}
// FIX: segregate by client need.
interface Coder { void code(); }
interface Rechargeable { void chargeBattery(); }
class RobotDev implements Coder, Rechargeable {
public void code() {}
public void chargeBattery() {}
}Explain without notes
Which SOLID letter is most violated by a 900-line controller with three responsibilities?
Give a real Liskov violation (Square extends Rectangle).
Practice
Find a switch statement over object types in your own code and replace it with a strategy map.
Trade-offs
- ↔
SOLID purity costs indirection; for a throwaway script, YAGNI wins.
Completion checklist
I can explain each letter with the production problem it prevents.