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)