DEV Community

Cover image for SOLID: Understanding the Principles That Make Code Cleaner and More Maintainable
Jean Klebert de A Modesto
Jean Klebert de A Modesto

Posted on

SOLID: Understanding the Principles That Make Code Cleaner and More Maintainable

If you’ve ever worked on a project that turned into a “monster” that’s hard to maintain, you probably felt the lack of good code design practices. That’s exactly where SOLID comes in.

SOLID is a set of five object-oriented programming principles that help you create code that is more organized, easier to maintain, test, and evolve.

In this article, we’ll understand each principle in simple terms, with practical examples, without unnecessary complexity.


What Is SOLID?

SOLID is an acronym created by Robert C. Martin (Uncle Bob) and stands for:

  • S – Single Responsibility Principle

  • O – Open/Closed Principle

  • L – Liskov Substitution Principle

  • I – Interface Segregation Principle

  • D – Dependency Inversion Principle

Let’s go through each one.


1️⃣ S — Single Responsibility Principle

A class should have only one reason to change.

❌ Bad example

A class that does too much:

class Report {
    void generateReport() {}
    void saveToDatabase() {}
    void sendEmail() {}
}

Enter fullscreen mode Exit fullscreen mode

This class:

  • Generates reports
  • Saves data
  • Sends emails

If any of these rules change, the entire class may break.

✅ Correct example

class ReportGenerator {
    void generate() {}
}

class ReportRepository {
    void save() {}
}

class EmailService {
    void send() {}
}

Enter fullscreen mode Exit fullscreen mode

📌 Benefit: More organized code that’s easier to test and maintain.


2️⃣ O — Open/Closed Principle

Software entities should be open for extension, but closed for modification.

❌ Bad example

class DiscountCalculator {
    double calculate(String type) {
        if (type.equals("CHRISTMAS")) return 0.2;
        if (type.equals("BLACK_FRIDAY")) return 0.3;
        return 0;
    }
}

Enter fullscreen mode Exit fullscreen mode

Every time a new discount appears, you must modify this class.

✅ Correct example

interface Discount {
    double calculate();
}

class ChristmasDiscount implements Discount {
    public double calculate() {
        return 0.2;
    }
}

class BlackFridayDiscount implements Discount {
    public double calculate() {
        return 0.3;
    }
}

Enter fullscreen mode Exit fullscreen mode

📌 Benefit: You add new behaviors without changing existing code.


3️⃣ L — Liskov Substitution Principle

A subclass should be replaceable by its superclass without breaking the system.

❌ Classic problematic example

class Bird {
    void fly() {}
}

class Penguin extends Bird {
    // Penguins don’t fly!
}

Enter fullscreen mode Exit fullscreen mode

This is a conceptual error: not every bird can fly.

✅ Correct example

class Bird {}

class FlyingBird extends Bird {
    void fly() {}
}

class Penguin extends Bird {}

Enter fullscreen mode Exit fullscreen mode

📌 Benefit: Correct inheritance hierarchies and fewer unexpected bugs.


4️⃣ I — Interface Segregation Principle

No class should be forced to implement methods it does not use.

❌ Bad example

interface Employee {
    void work();
    void eat();
}

Enter fullscreen mode Exit fullscreen mode

What if we have a robot?

class Robot implements Employee {
    public void work() {}
    public void eat() {} // Makes no sense
}

Enter fullscreen mode Exit fullscreen mode

✅ Correct example

interface Worker {
    void work();
}

interface Eater {
    void eat();
}

Enter fullscreen mode Exit fullscreen mode
class Human implements Worker, Eater {}
class Robot implements Worker {}

Enter fullscreen mode Exit fullscreen mode

📌 Benefit: Smaller, more specific, and reusable interfaces.


5️⃣ D — Dependency Inversion Principle

Depend on abstractions, not on concrete implementations.

❌ Bad example

class OrderService {
    private MySQLDatabase db = new MySQLDatabase();
}

Enter fullscreen mode Exit fullscreen mode

Here, the code is tightly coupled to MySQL.

✅ Correct example

interface Database {
    void save();
}

class MySQLDatabase implements Database {
    public void save() {}
}

class OrderService {
    private Database db;

    OrderService(Database db) {
        this.db = db;
    }
}

Enter fullscreen mode Exit fullscreen mode

📌 Benefit: Flexible code that’s easy to test (with mocks) and easy to change implementations.


🚀 Conclusion

Applying SOLID is not about:

❌ Writing complex code

❌ Using patterns everywhere

It’s about:

✅ Organization

✅ Maintainability

✅ Scalability

✅ Fewer bugs in the future


You don’t need to apply all principles all the time, but understanding them helps you make better design decisions.

Top comments (0)