SOLID: LSP, ISP, and DIP
The remaining three SOLID principles - LSP, ISP, and DIP - govern how classes and interfaces relate to each other. These principles prevent the subtle bugs and architectural decay that emerge when inheritance and dependencies are misused. Master these, and you'll design systems that remain flexible for years.
π At a Glance
| Aspect | Details |
|---|---|
| Principles Covered | LSP, ISP, DIP |
| Key Concept | Contracts and abstractions |
| Detection | UnsupportedOperationException, fat interfaces |
| Primary Pattern | Interface extraction, dependency injection |
| Business Impact | 70% reduction in integration bugs |
π― What You'll Learn
After reading this article, you will be able to:
- Explain LSP with the Rectangle/Square example and real-world cases
- Identify LSP violations through behavioral contract analysis
- Detect fat interfaces and apply Interface Segregation
- Distinguish DIP from DI (they're not the same!)
- Design abstractions that don't leak implementation details
- Apply these principles in Spring Boot applications
π₯ Production Story: The Substitution That Broke Production
An e-commerce platform had a class hierarchy for product inventory:
JAVA(31 lines)CodeLoading syntax highlighter...
JAVA(7 lines)CodeLoading syntax highlighter...
- 2,340 orders failed during Black Friday
- $180,000 in lost sales
- Customer trust damaged
- 3-day emergency fix
setQuantity() contract.JAVA(24 lines)CodeLoading syntax highlighter...
π§ Mental Model: Contracts and Substitutability
TEXT(26 lines)CodeLoading syntax highlighter...
TEXT(11 lines)CodeLoading syntax highlighter...
π¬ Deep Dive
Part 1: Liskov Substitution Principle (LSP)
- Preconditions cannot be strengthened - Subtype must accept everything the parent accepts
- Postconditions cannot be weakened - Subtype must guarantee at least what parent guarantees
- Invariants must be preserved - If parent says "X is always true", subtype must maintain X
- History constraint - Subtype cannot allow state changes parent would prohibit
Detecting LSP Violations
JAVA(32 lines)CodeLoading syntax highlighter...
Fixing LSP Violations
JAVA(33 lines)CodeLoading syntax highlighter...
JAVA(42 lines)CodeLoading syntax highlighter...
JAVA(24 lines)CodeLoading syntax highlighter...
Part 2: Interface Segregation Principle (ISP)
Detecting ISP Violations
JAVA(19 lines)CodeLoading syntax highlighter...
Refactoring to ISP
TEXT(4 lines)CodeLoading syntax highlighter...
JAVA(35 lines)CodeLoading syntax highlighter...
ISP in Real-World Java
JAVA(23 lines)CodeLoading syntax highlighter...
JAVA(26 lines)CodeLoading syntax highlighter...
Part 3: Dependency Inversion Principle (DIP)
- High-level modules should not depend on low-level modules. Both should depend on abstractions.
- Abstractions should not depend on details. Details should depend on abstractions.
TEXT(8 lines)CodeLoading syntax highlighter...
The Abstraction Ownership Problem
TEXT(11 lines)CodeLoading syntax highlighter...
TEXT(17 lines)CodeLoading syntax highlighter...
Implementing DIP in Spring
JAVA(43 lines)CodeLoading syntax highlighter...
Abstractions That Don't Leak
JAVA(10 lines)CodeLoading syntax highlighter...
JAVA(12 lines)CodeLoading syntax highlighter...
β οΈ Common Mistakes
Mistake 1: Marker Interfaces That Lie
JAVA(7 lines)CodeLoading syntax highlighter...
Mistake 2: Interface for Everything
JAVA(16 lines)CodeLoading syntax highlighter...
Mistake 3: Wrong Abstraction Level
JAVA(13 lines)CodeLoading syntax highlighter...
Mistake 4: Circular Dependencies via Abstraction
JAVA(23 lines)CodeLoading syntax highlighter...
π Debug This: The Square-Rectangle Paradox
Square extends Rectangle and my test says area should be 20, but I'm getting 25!"JAVA(39 lines)CodeLoading syntax highlighter...
Square violates LSP because it doesn't behave like a Rectangle should. A client using Rectangle expects:setWidth(4)followed bysetHeight(5)β area = 20- Width and height are independent
Square couples them, breaking this expectation.rect.setWidth(4)sets both width AND height to 4rect.setHeight(5)sets both width AND height to 5- Result: 5 Γ 5 = 25, not 4 Γ 5 = 20
π» Exercises
Exercise 1: Fix the LSP Violation
ββ Difficulty: Medium | β±οΈ Time: 15 minutes
JAVA(21 lines)CodeLoading syntax highlighter...
JAVA(24 lines)CodeLoading syntax highlighter...
Exercise 2: Apply ISP
ββ Difficulty: Medium | β±οΈ Time: 20 minutes
JAVA(16 lines)CodeLoading syntax highlighter...
JAVA(24 lines)CodeLoading syntax highlighter...
Exercise 3: Apply DIP
βββ Difficulty: Medium-Hard | β±οΈ Time: 20 minutes
JAVA(20 lines)CodeLoading syntax highlighter...
JAVA(27 lines)CodeLoading syntax highlighter...
Exercise 4: Design Service Layer with DIP
βββ Difficulty: Medium-Hard | β±οΈ Time: 25 minutes
JAVA(10 lines)CodeLoading syntax highlighter...
JAVA(34 lines)CodeLoading syntax highlighter...
Exercise 5: Complete SOLID Audit
ββββ Difficulty: Hard | β±οΈ Time: 30 minutes
JAVA(24 lines)CodeLoading syntax highlighter...
JAVA(54 lines)CodeLoading syntax highlighter...
π€ Senior-Level Interview Questions
Question #1: Explain LSP with a real example from your experience.
Cache interface with get() and put() methods. We created a ReadOnlyCache that threw exceptions on put().JAVA(3 lines)CodeLoading syntax highlighter...
ReadableCache and WritableCache:JAVA(7 lines)CodeLoading syntax highlighter...
ReadableCache.Question #2: How does ISP relate to microservices API design?
ISP applies to APIs just like interfaces:
/api/orders - GET, POST, PUT, DELETE, GET analytics, GET reports, POST bulk, etc.
Mobile client only needs GET and POST but must handle entire contract.
/api/orders - Core CRUD (mobile client uses this) /api/orders/analytics - Separate endpoint (dashboard uses this) /api/orders/bulk - Separate endpoint (admin uses this)
Even better: GraphQL lets clients request exactly what they need.
Question #3: What's the difference between DIP and DI frameworks?
JAVA(6 lines)CodeLoading syntax highlighter...
JAVA(7 lines)CodeLoading syntax highlighter...
Question #4: How do you test code that follows DIP?
DIP makes testing trivial because you depend on abstractions:
JAVA(25 lines)CodeLoading syntax highlighter...
Question #5: When is it OK to violate these principles?
- Null Object pattern (intentionally different behavior)
- Decorator that adds preconditions (with documentation)
- Legacy wrapper gradually migrating behavior
- Small internal interfaces where splitting adds complexity
- Performance-critical code where virtual dispatch matters
- Marker interfaces (empty by design)
- Simple applications without swappable implementations
- Performance-critical hot paths
- Frameworks/libraries (they ARE the low-level module)
Question #6: How do these principles interact with each other?
They reinforce each other:
- Small, focused interfaces have clearer contracts
- Easier to implement fully (no UnsupportedOperationException)
- High-level module defines focused interface it needs
- Low-level implements only what's required
- Substitutable implementations make abstraction meaningful
- Can swap implementations confidently
- Testable code (mock small interfaces)
- Flexible architecture (swap implementations)
- Stable high-level modules (insulated from changes)
π Summary & Key Takeaways
Liskov Substitution Principle
| Aspect | Key Point |
|---|---|
| Definition | Subtypes must be substitutable for base types |
| Rules | Preconditions, postconditions, invariants |
| Detection | UnsupportedOperationException, instanceof checks |
| Fix | Extract interface, composition, DbC |
Interface Segregation Principle
| Aspect | Key Point |
|---|---|
| Definition | Clients shouldn't depend on unused methods |
| Detection | Empty implementations, UnsupportedOperationException |
| Fix | Split into role-based interfaces |
| Benefit | Smaller contracts, easier implementation |
Dependency Inversion Principle
| Aspect | Key Point |
|---|---|
| Definition | Depend on abstractions, not concretions |
| Key insight | High-level owns the abstraction |
| Not the same as | Dependency Injection (DI is technique, DIP is principle) |
| Benefit | Swappable implementations, testability |
SOLID Summary Table
| Principle | One-liner |
|---|---|
| SRP | One reason to change |
| OCP | Extend without modifying |
| LSP | Subtypes honor contracts |
| ISP | Small, focused interfaces |
| DIP | Depend on abstractions |
π Conclusion
LSP, ISP, and DIP complete the SOLID foundation. Together with SRP and OCP, they provide a framework for designing maintainable object-oriented systems.
- LSP is about contracts, not just inheritance
- ISP is about client needs, not interface size
- DIP is about ownership, not just abstraction
π Review Schedule for This Article
| Day | Task | Time |
|---|---|---|
| Day 1 | Review LSP behavioral substitutability concept | 5 min |
| Day 3 | Redo Exercise 1 (Fix LSP Violation - Bird/Ostrich) | 15 min |
| Day 7 | Answer interview questions without looking | 10 min |
| Day 14 | Redo Debug This (Square-Rectangle Paradox) | 10 min |
| Day 30 | Review ISP in your current project - are there fat interfaces? | 10 min |