Expert · Design patterns, JVM internals and interviews · topic 56 of 60 · 30 min
SOLID principles
Explanation
Why principles?
Code that works today must change tomorrow: new payment methods, new tax rules, new report formats. Badly structured code makes every change risky. SOLID, named by Robert C. Martin, summarises habits that make change cheaper. Treat them as guidelines, not laws.
S: Single responsibility
// Before: one class with three reasons to change
class InvoiceService {
double total(Invoice i) { … } // tax rules change
byte[] toPdf(Invoice i) { … } // PDF layout changes
void email(Invoice i) { … } // email provider changes
}
// After: one job each
class InvoiceCalculator { double total(Invoice i) { … } }
class InvoicePdfRenderer { byte[] render(Invoice i) { … } }
class InvoiceMailer { void send(Invoice i, byte[] pdf) { … } }O: Open/closed
Classes should be open for extension but closed for modification: add new behaviour by adding code, not by editing a growing if/else chain. Polymorphism is the usual tool.
interface DiscountRule { double apply(double amount); }
class FestivalDiscount implements DiscountRule { public double apply(double a) { return a * 0.9; } }
class MemberDiscount implements DiscountRule { public double apply(double a) { return a > 500 ? a - 50 : a; } }
// A new rule = a new class; the checkout code that loops over List<DiscountRule> never changes.L: Liskov substitution
Anywhere the code expects a parent type, any subclass must work without surprises. The classic violation: Square extends Rectangle. Setting a Rectangle's width should not change its height, but a Square must keep both equal, so code that works with rectangles breaks with squares. If a subclass has to throw UnsupportedOperationException for an inherited method, the inheritance is probably wrong.
I: Interface segregation
// Too big: a simple printer must implement scan and fax
interface OfficeMachine { void print(Doc d); void scan(Doc d); void fax(Doc d); }
// Better: small, focused interfaces
interface Printer { void print(Doc d); }
interface Scanner { void scan(Doc d); }
class BasicPrinter implements Printer { public void print(Doc d) { … } }D: Dependency inversion
High-level code (OrderService) should depend on abstractions (a PaymentGateway interface), not on concrete details (RazorpayGateway). The details implement the abstraction. Spring's dependency injection (module 11) is built around this principle.
Need help?
Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.
Try:
Watch the lesson
SOLID principles · 1/6
In this lesson you will learn the five SOLID principles of object-oriented design.
Prefer a video by a teacher? Find free videos on “solid principles java examples explained” on YouTube ↗
In real life: Adding a new tax rule without touching checkout
An e-commerce company had GST logic inside a 600-line checkout method. Every tax change risked breaking payments. Refactoring into a TaxRule interface with one class per rule meant the next GST change was a new 20-line class and its tests, with the checkout code untouched.
interface TaxRule { boolean appliesTo(Item i); long taxPaise(Item i); }
long totalTax(List<Item> items, List<TaxRule> rules) {
return items.stream()
.mapToLong(i -> rules.stream().filter(r -> r.appliesTo(i)).mapToLong(r -> r.taxPaise(i)).sum())
.sum();
}Practice (0/3 done)
class ReportService { load data; calculate totals; format as Excel; email it } violates mainly…
A ReadOnlyList extends ArrayList and throws UnsupportedOperationException from add(). Which principle is broken?
Take a method that calculates shipping with if/else on the courier name (if "BlueDart" … else if "Delhivery" … else if "IndiaPost" …). Refactor it with an interface ShippingRate and one class per courier, so adding a new courier needs no change to existing code. Name which SOLID principles your refactor applies.
Quick quiz
Read more (free)
Related help
Before this, learn
This topic builds on these. If something here feels new, open them first.
Related topics
- Common design patternsDesign patterns, JVM internals and interviews · same module
- JVM memory and garbage collectionDesign patterns, JVM internals and interviews · same module
- Java interview questionsDesign patterns, JVM internals and interviews · same module
- Inheritance and interfacesKotlin and Android App Development · Classes, objects and collections
- Inheritance, interfaces, mixins and extensionsFlutter and Dart · Null safety and object-oriented Dart
- Test doubles: fakes, stubs and mocksC# and .NET · Testing, tooling and Unity
Questions people ask about this
Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.
What does the O in SOLID stand for?
Answer: Open/closed
Open for extension, closed for modification.
Dependency inversion says high-level code should depend on…
Answer: Abstractions (interfaces)
Details implement the abstractions.
What is a risk of applying SOLID everywhere from day one?
Answer: Over-engineering with needless interfaces and layers
Add abstractions when real variation appears.
Interface segregation recommends…
Answer: Small, focused interfaces
Clients should not depend on methods they do not use.
How many classes can a Kotlin class extend?Related
How many classes can a Dart class extend?Related
Answer: One
Single inheritance; many interfaces and mixins.
Which annotation creates a bean from a method in a configuration class?Related
How many classes can a Java class directly extend?Related
Pass the quiz (60% or more) or mark this topic as done when you are ready.
Your progress is saved on this device only.
Next steps
- 4 more topics in “Design patterns, JVM internals and interviews” before its exam: Common design patterns, JVM memory and garbage collection, Java interview questions, Projects and a Java career.
- Module 12 exam: Design patterns, JVM internals and interviews · 12 questions · 25 min, without help. 5 topics of this module still to finish first.
- 60 topics left in the course before the final exam.