Skip to main content

Command Palette

Search for a command to run...

SOLID Principle

Published
4 min read

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

PrincipleExplanationKey Benefit
SSingle Responsibility PrincipleImproves maintainability
OOpen/Closed PrincipleAllows extension without modification
LLiskov Substitution PrincipleEnsures correct inheritance usage
IInterface Segregation PrinciplePrevents unnecessary implementations
DDependency Inversion PrincipleReduces tight coupling