SOLID is a set of five object-oriented design principles that help developers write maintainable, scalable, and robust code in Java.
Why do you need SOLID?
SOLID is a set of object-oriented programming principles introduced by Robert Martin (Uncle Bob) in 1995. Their idea is to avoid dependencies between code components. If there are a large number of dependencies, such code is difficult to maintain.
Its main problems are:
- Rigidity: each change causes many other changes
- Fragility: changes in one part break the work of other parts
- Immobility: code cannot be reused outside of its context
1. S – Single Responsibility Principle (SRP)
A class should have only one reason to change.
Each class should focus on one responsibility only to improve maintainability.
✅ Example
// Violating SRP: Class handles both user data & database operations
class User {
void saveToDatabase() { /* Save user to DB */ }
}
// Applying SRP: Separate responsibilities
class User { /* User properties and methods */ }
class UserRepository {
void save(User user) { /* Save user to DB */ }
}
🔹 Why? The User
class should not handle persistence logic.
2. O – Open/Closed Principle (OCP)
Software entities should be open for extension but closed for modification.
Instead of modifying existing code, extend it using polymorphism (inheritance or interfaces).
✅ Example
// Violating OCP: Modifying existing code for new shape types
class Shape {
String type;
}
class AreaCalculator {
double calculate(Shape shape) {
if (shape.type.equals("Circle")) { /* logic */ }
else if (shape.type.equals("Rectangle")) { /* logic */ }
return 0;
}
}
// Applying OCP: Extend functionality without modifying existing code
interface Shape {
double calculateArea();
}
class Circle implements Shape {
double radius;
public double calculateArea() { return Math.PI * radius * radius; }
}
class Rectangle implements Shape {
double length, width;
public double calculateArea() { return length * width; }
}
🔹 Why? Adding a new shape (e.g., Triangle
) doesn’t modify existing code.
3. L – Liskov Substitution Principle (LSP)
Subtypes should be replaceable without altering correctness.
✅ Example
// Violating LSP: A subclass changes expected behavior
class Bird {
void fly() { System.out.println("Flying"); }
}
class Penguin extends Bird {
void fly() { throw new UnsupportedOperationException("Penguins can't fly"); }
}
🔹 Issue? A Penguin
is a Bird
but cannot fly, violating expectations.
✅ Applying LSP
interface Bird { }
interface FlyingBird extends Bird {
void fly();
}
class Sparrow implements FlyingBird {
public void fly() { System.out.println("Flying"); }
}
class Penguin implements Bird { /* No fly method */ }
🔹 Why? Penguin
now correctly follows expectations without modifying behavior.
4. I – Interface Segregation Principle (ISP)
Clients should not be forced to implement unnecessary methods.
Large interfaces should be broken into smaller, more specific interfaces.
✅ Example
// Violating ISP: A class is forced to implement unrelated methods
interface Worker {
void work();
void eat();
}
class Robot implements Worker {
public void work() { /* Works */ }
public void eat() { throw new UnsupportedOperationException("Robots don't eat"); }
}
// Applying ISP: Split into multiple interfaces
interface Workable {
void work();
}
interface Eatable {
void eat();
}
class Human implements Workable, Eatable {
public void work() { /* Works */ }
public void eat() { /* Eats */ }
}
class Robot implements Workable {
public void work() { /* Works */ }
}
🔹 Why? Now, Robot
isn’t forced to implement eat()
.
5. D – Dependency Inversion Principle (DIP)
High-level modules should not depend on low-level modules. Both should depend on abstractions.
Bad Example (Violating DIP)
Here, Application
directly depends on MySQLDatabase
.
class MySQLDatabase {
void connect() { System.out.println("Connecting to MySQL"); }
}
class Application {
private MySQLDatabase db = new MySQLDatabase();
void start() { db.connect(); }
}
Why is this bad?
- If we switch databases, we must modify
Application
.
Correct Example (Applying DIP)
Instead of hardcoding dependencies, we use an interface:
interface Database {
void connect();
}
class MySQLDatabase implements Database {
public void connect() { System.out.println("Connecting to MySQL"); }
}
class Application {
private Database db;
public Application(Database db) { this.db = db; }
void start() { db.connect(); }
}
Now, we can switch to PostgreSQL without modifying Application
.
Conclusion
SOLID principles make Java code:
✔ More maintainable
✔ More scalable
✔ Less coupled
✔ Easier to test
Read other awesome articles in Medium.com or in akcoding’s posts.
OR
Join us on YouTube Channel
OR Scan the QR Code to Directly open the Channel 👉
