SOLID Principle
The SOLID principles in Java (or any object-oriented programming language) are a set of design principles that help create scalable, maintainable, and flexible code. They were introduced by Robert C. Martin (Uncle Bob) and stand for:
1. S - Single Responsibility Principle (SRP)
A class should have only one reason to change.
This means that a class should have only one job or responsibility.
✅ Example of SRP:
javaCopyEdit// Bad: A single class handling multiple responsibilities
class Employee {
void calculateSalary() { /* Salary Calculation */ }
void printPaySlip() { /* Print Salary Slip */ }
void saveToDatabase() { /* Save Employee to DB */ }
}
// Good: Separate classes for different responsibilities
class SalaryCalculator {
void calculateSalary() { /* Salary Calculation */ }
}
class PaySlipPrinter {
void printPaySlip() { /* Print Salary Slip */ }
}
class EmployeeRepository {
void saveToDatabase() { /* Save Employee to DB */ }
}
2. O - Open/Closed Principle (OCP)
A class should be open for extension but closed for modification.
This means that you should be able to add new functionality without modifying existing code.
✅ Example of OCP:
javaCopyEdit// Bad: Modifying the existing class whenever a new shape is added
class Rectangle {
double length, width;
double area() { return length * width; }
}
class AreaCalculator {
double calculate(Object shape) {
if (shape instanceof Rectangle) {
Rectangle r = (Rectangle) shape;
return r.area();
}
return 0;
}
}
// Good: Using polymorphism to extend functionality without modification
interface Shape {
double area();
}
class Rectangle implements Shape {
double length, width;
public double area() { return length * width; }
}
class Circle implements Shape {
double radius;
public double area() { return Math.PI * radius * radius; }
}
class AreaCalculator {
double calculate(Shape shape) {
return shape.area(); // No need to modify this class when adding new shapes
}
}
3. L - Liskov Substitution Principle (LSP)
Subtypes must be substitutable for their base types.
A child class should not break the behavior of its parent class.
✅ Example of LSP:
javaCopyEdit// Bad: A subclass that changes the expected behavior
class Bird {
void fly() { System.out.println("Flying..."); }
}
class Penguin extends Bird {
void fly() { throw new RuntimeException("Penguins can't fly!"); } // Violates LSP
}
// Good: Use an appropriate hierarchy
abstract class Bird {}
abstract class FlyingBird extends Bird { abstract void fly(); }
class Sparrow extends FlyingBird {
public void fly() { System.out.println("Flying..."); }
}
class Penguin extends Bird {} // No fly method, so it respects LSP
4. I - Interface Segregation Principle (ISP)
Clients should not be forced to implement interfaces they do not use.
A single, large interface should be split into smaller, more specific interfaces.
✅ Example of ISP:
javaCopyEdit// Bad: A large interface forcing unnecessary method implementations
interface Worker {
void work();
void eat();
}
class Robot implements Worker {
public void work() { System.out.println("Working..."); }
public void eat() { throw new UnsupportedOperationException("Robots don't eat!"); } // Violates ISP
}
// Good: Split into smaller interfaces
interface Workable { void work(); }
interface Eatable { void eat(); }
class HumanWorker implements Workable, Eatable {
public void work() { System.out.println("Working..."); }
public void eat() { System.out.println("Eating..."); }
}
class RobotWorker implements Workable {
public void work() { System.out.println("Working..."); }
}
5. D - Dependency Inversion Principle (DIP)
High-level modules should not depend on low-level modules. Both should depend on abstractions.
Instead of tightly coupling classes to concrete implementations, depend on abstractions (interfaces).
✅ Example of DIP:
javaCopyEdit// Bad: High-level class depends on a low-level class
class MySQLDatabase {
void connect() { System.out.println("Connecting to MySQL"); }
}
class Application {
private MySQLDatabase database = new MySQLDatabase(); // Tight coupling
void start() {
database.connect();
}
}
// Good: Depend on an abstraction (interface)
interface Database {
void connect();
}
class MySQLDatabase implements Database {
public void connect() { System.out.println("Connecting to MySQL"); }
}
class Application {
private Database database;
Application(Database database) {
this.database = database;
}
void start() {
database.connect();
}
}
// Now we can easily switch to a different database implementation
class PostgreSQLDatabase implements Database {
public void connect() { System.out.println("Connecting to PostgreSQL"); }
}
Summary
| Principle | Explanation | Key Benefit |
| S | Single Responsibility Principle | Improves maintainability |
| O | Open/Closed Principle | Allows extension without modification |
| L | Liskov Substitution Principle | Ensures correct inheritance usage |
| I | Interface Segregation Principle | Prevents unnecessary implementations |
| D | Dependency Inversion Principle | Reduces tight coupling |