Everyone knows, but it's worth remembering ๐
Hi fellow developers!
My topic today is about SOLID. I decided to write about it to learn more and share this content with you.
SOLID is a set of 5 essential principles that help developers write clean, maintainable, and scalable object-oriented code. In this article, weโll dive deep into each principle with clear explanations and real Java examples.
๐ What is SOLID?
S.O.L.I.D is an acronym introduced by Robert C. Martin (Uncle Bob) that stands for:
- S: Single Responsibility Principle
- O: Open/Closed Principle
- L: Liskov Substitution Principle
- I: Interface Segregation Principle
- D: Dependency Inversion Principle
These principles guide developers in designing software thatโs:
- Easier to maintain
- More reusable
- More readable
- Less prone to bugs
- Easier to test and scale
Let's take a closer look at each letter.
๐น 1. Single Responsibility Principle (SRP)
๐ โA class should have only one reason to change.โ Each class should do one thing and do it well.
โ
Why?
If a class has multiple responsibilities, changes to one responsibility might break the others.
โ
Example:
Use interfaces or abstract classes and inheritance to extend behavior.
๐ Bad Example:
public class InvoiceService { public void calculateTotal() { /* ... */ } public void saveToDatabase() { /* ... */ } public void printInvoice() { /* ... */ } } In my opinion this class isn't a good example to try to explain this principle. I don't have any idea in my mind, but I hope it helps you understand.๐คฃ
This class mixes business logic, persistence, and presentation - all in one. ๐ This situation is not something you would encounter in real life, in a real project or on your project. At least that's what I hope ๐๐
This class is hard to test, maintain and opened to create a bug
โ
Good Example:
public interface Calculator<T> { voic calculate(T input); } public class InvoiceCalculator implements Calculator<Invoice> { public void calculate(Invoice input) { /* ... */ } } public interface InvoiceRepository { public void save(Invoice invoice); } public class InvoiceDAO implements InvoiceRepository { public void save(Invoice invoice) { /* ... */} } public interface Printer<T> { public void print(T input); } public class InvoicePrinter implements Printer<Invoice> { public void print(Invoice info) {/* ... */} } Each class now has a single responsibility.
Maybe, we could use a functional interface like Function or Consumer and take advantage of java language features.
What's do you think about it?
๐น 2. Open/Closed Principle (OCP)
๐ โSoftware entities should be open for extension but closed for modification.โ
โ
Why?
Modifying existing code introduces risk. Extending it (e.g., via inheritance or composition) avoids breaking tested code.
๐ Bad Example:
public class Payment { public void process(String method) { if (method.equals("CreditCard")) { // ... } else if (method.equals("PayPal")) { // ... } } } โ
Good Example:
public interface PaymentMethod { void pay(); } public class CreditCardPayment implements PaymentMethod { public void pay() { System.out.println("Paying with credit card"); } } public class PayPalPayment implements PaymentMethod { public void pay() { System.out.println("Paying with PayPal"); } } public class PaymentProcessor { public void process(PaymentMethod method) { method.pay(); // Easily extend by adding new PaymentMethod types } } No need to modify PaymentProcessor to add new methods โ just extend PaymentMethod.
๐น 3. Liskov Substitution Principle (LSP)
๐ โSubtypes must be substitutable for their base types.โ
A subclass should be able to replace its parent class without breaking the program.
โ Why?
Violating this leads to code that crashes or behaves incorrectly when using polymorphism.
๐ Bad Example:
class Bird { public void fly() { System.out.println("Flying"); } } class Ostrich extends Bird { @Override public void fly() { throw new UnsupportedOperationException() } } Calling fly() on Bird works for most birds, but fails for Ostrich โ violating LSP.
โ
Good Example (Refactored Design):
abstract class Bird {} interface Flyable { void fly(); } class Sparrow extends Bird implements Flyable { public void fly() { System.out.println("Sparrow flying"); } } class Ostrich extends Bird { // Doesn't implement Flyable } Now, only birds that can fly implement Flyable, respecting LSP.
๐น 4. Interface Segregation Principle (ISP)
๐ โClients should not be forced to depend on methods they do not use.โ
Split large, bloated interfaces into smaller, more specific ones.
โ
Why?
A class should only implement the methods it needs. Large interfaces cause unnecessary complexity.
๐ Bad Example:
public interface Machine { void print(); void scan(); void fax(); } public class SimplePrinter implements Machine { public void print() { System.out.println("Printing..."); } public void scan() { /* Not needed */ } public void fax() { /* Not needed */ } } This forces SimplePrinter to implement unused methods.
โ
Good Example:
public interface Printer { void print(); } public interface Scanner { void scan(); } public class SimplePrinter implements Printer { public void print() { System.out.println("Printing..."); } } Each class now depends only on what it actually uses.
๐น 5. Dependency Inversion Principle (DIP)
๐ โHigh-level modules should not depend on low-level modules. Both should depend on abstractions.โ
Depend on interfaces, not concrete implementations.
โ
Why?
This promotes flexibility, testability, and decoupling.
๐ Bad Example:
public class OrderService { private PaypalProcessor processor = new PaypalProcessor(); // tightly coupled public void checkout() { processor.pay(); } } โ
Good Example:
public interface PaymentProcessor { void pay(); } public class PaypalProcessor implements PaymentProcessor { public void pay() { System.out.println("Paid with PayPal"); } } public class OrderService { private final PaymentProcessor processor; public OrderService(PaymentProcessor processor) { this.processor = processor; } public void checkout() { processor.pay(); } } Now, we can easily swap PaypalProcessor for another processor or mock it in tests.
๐ Summary Table
| Principle | Focus | Goal |
|---|---|---|
| S | Single Responsibility | One reason to change |
| O | Open/Closed | Extend without modifying |
| L | Liskov Substitution | Replace parent without breaking behavior |
| I | Interface Segregation | Donโt force classes to depend on unused methods |
| D | Dependency Inversion | Depend on abstractions, not concrete classes |
Done!
I hope this post is useful for me ๐
and you now and in the future, and that you will find it helpful. ๐
If you have any opinions, criticisms or ideas, don't hesitate to get in touch.
Thank you very much
See ya!
Top comments (0)