SOLID Principles

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 👉

AK Coding YouTube Channel

Share with