β Summary & Cheat Sheet
Congratulations! You've learned all 5 SOLID principles. Here's everything at a glance.
πΊοΈ The Big Pictureβ
S β Single Responsibility β One class, one job
O β Open/Closed β Extend, don't modify
L β Liskov Substitution β Subclasses keep parent's promise
I β Interface Segregation β Small, focused interfaces
D β Dependency Inversion β Depend on interfaces, not concretions
π Principle Quick Referenceβ
S β Single Responsibilityβ
- Rule: A class should have only one reason to change
- Smell: The class description needs the word "AND"
- Fix: Extract each responsibility into its own class
- Spring example: Separate
@Controller,@Service,@Repository
// β Bad
class UserService { void save() {} void sendEmail() {} void log() {} }
// β
Good
class UserRepository { void save() {} }
class EmailService { void send() {} }
class AuditLogger { void log() {} }
O β Open/Closedβ
- Rule: Open for extension, closed for modification
- Smell: Adding a feature requires adding another
if/elseto an existing class - Fix: Use interfaces and polymorphism; add new classes instead of changing old ones
- Spring example: Strategy pattern with
@Componentbeans in aMap
// β Bad
if (type.equals("EMAIL")) { ... }
else if (type.equals("SMS")) { ... }
// β
Good
interface NotificationSender { void send(String msg); }
class EmailSender implements NotificationSender { ... }
class SmsSender implements NotificationSender { ... }
L β Liskov Substitutionβ
- Rule: Subclasses must be drop-in replacements for their parent
- Smell: A subclass throws
UnsupportedOperationExceptionor has an empty method body - Fix: Don't inherit just for code reuse β inherit for true "is-a" relationships; use interfaces instead
- Spring example: All
PaymentGatewayimplementations actually supportcharge()
// β Bad: Square extends Rectangle breaks area calculation
// β
Good: Both implement Shape interface with getArea()
interface Shape { int getArea(); }
class Rectangle implements Shape { ... }
class Square implements Shape { ... }
I β Interface Segregationβ
- Rule: Don't force classes to implement methods they don't use
- Smell: Empty method bodies or
// not applicablecomments in implementations - Fix: Break fat interfaces into small, focused ones
- Spring example: Separate read/write repositories; separate sender interfaces
// β Bad
interface Worker { void work(); void eat(); void sleep(); }
// β
Good
interface Workable { void work(); }
interface Eatable { void eat(); }
interface Sleepable { void sleep(); }
D β Dependency Inversionβ
- Rule: Depend on abstractions (interfaces), not concrete classes
- Smell:
new ConcreteClass()inside a service or business logic class - Fix: Inject dependencies through constructor; depend on interfaces
- Spring example:
@Autowiredwith interface types;@Profileto swap implementations
// β Bad
class OrderService {
private StripeGateway gateway = new StripeGateway(); // hardcoded!
}
// β
Good
class OrderService {
private final PaymentGateway gateway; // interface!
public OrderService(PaymentGateway gateway) { this.gateway = gateway; }
}
π How They Work Togetherβ
The SOLID principles aren't isolated rules β they complement each other:
| Principle | Enables |
|---|---|
| SRP | Smaller, focused classes β easier to apply OCP and DIP |
| OCP | Uses interfaces (ISP) and injection (DIP) to allow extension |
| LSP | Ensures that OCP's substitutions actually work correctly |
| ISP | Makes DIP easier β small interfaces are easier to implement and inject |
| DIP | The foundation for testability and flexibility in Spring |
π§ͺ Testing Is Your Signalβ
If a class is hard to unit test, it's usually violating one or more SOLID principles:
| Symptom | Likely Violation |
|---|---|
| Can't test without a real database | D β no interface for repository |
| Test class setup is 100 lines | S β class does too much |
| Adding a new case breaks existing tests | O β not open for extension |
| Mock throws unexpectedly | L β subclass doesn't honor contract |
| Mocking forces you to stub 10 unused methods | I β interface is too fat |
π― Where to Go Nextβ
- Practice: Refactor a class in your current project using these principles
- Read: Clean Code by Robert C. Martin
- Learn: Design patterns (Strategy, Factory, Observer) β they all build on SOLID
- Explore: Spring's source code itself is a masterclass in SOLID applied to real frameworks
Interview Questionsβ
Q: How do SOLID principles interact when they appear to conflict?β
A: Treat them as balancing constraints, not absolute rules. For example, using OCP may introduce abstractions (DIP/ISP), but if abstraction cost is too high for a stable module, keep design simpler and refactor later.
Q: Give an example where strict OCP can hurt maintainability.β
A: If every small variation creates a new strategy class, teams may end up with class explosion and weak discoverability. For low-volatility logic, a simple branch can be more maintainable.
Q: How would you review a pull request for SOLID quality quickly?β
A: Check responsibility boundaries, dependency direction, contract safety, and interface size. Then verify if the change made testing easier or harder.
Q: What architecture smell shows both SRP and ISP violations at once?β
A: A service with many public methods serving unrelated use cases, plus consumers that only use small subsets of those methods.
Q: How does DIP support clean architecture layering?β
A: Domain and application layers depend on interfaces owned by higher-level policies, while infrastructure implements those interfaces. This keeps business rules independent of framework/vendor details.
Q: When is it acceptable to violate a SOLID principle temporarily?β
A: During short-lived exploration or incident fixes, if you capture technical debt and schedule cleanup. The violation should be explicit, bounded, and reversible.
Q: How do you measure whether SOLID adoption is working over time?β
A: Observe trend improvements in defect rate, test execution speed, change lead time, and blast radius of modifications.
Q: What is your senior-level rule for applying SOLID pragmatically?β
A: Optimize for changeability. Apply stronger abstractions where requirements evolve frequently, and keep straightforward code where volatility is low.