DEV Community

Cover image for Write Code Like A Pro: Mastering The SOLID Principles
Gaurav Dalvi
Gaurav Dalvi

Posted on • Originally published at blog.gauravdalvi.com

3

Write Code Like A Pro: Mastering The SOLID Principles

If you're a developer, you've probably heard whispers of this ancient wisdom in code reviews, design docs, or the hushed conversations between two senior devs in the corner of your office:

"You should follow SOLID principles."

But what exactly are these? Some kind of secret cult? A new JavaScript framework? Fear not — SOLID is simply an acronym, and one of the best blueprints for writing maintainable, scalable, and... non-disaster-prone code.

Let’s break it down, with some real-world humor sprinkled in.


🧠 S — Single Responsibility Principle (SRP)

"One class, one reason to change."

Real-world analogy:
Imagine you hired a plumber to fix your sink, and halfway through the job, he starts giving you a lecture on tax planning. That’s what violating SRP feels like in code.

Code example:

Bad:

class UserManager {
    public void createUser() { /* ... */ }
    public void deleteUser() { /* ... */ }
    public void generateUserReport() { /* ... */ } // 🚨 Mixing concerns!
}
Enter fullscreen mode Exit fullscreen mode

Good:

class UserManager {
    public void createUser() { /* ... */ }
    public void deleteUser() { /* ... */ }
}
Enter fullscreen mode Exit fullscreen mode
class UserReportGenerator {
    public void generateUserReport() { /* ... */ }
}
Enter fullscreen mode Exit fullscreen mode

Each class now does one job. Fewer surprise side effects, fewer headaches.


🧠 O — Open/Closed Principle (OCP)

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

Real-world analogy:
When your phone gets a new feature, you install an app. You don’t break out a screwdriver and start rewiring the motherboard. Your code should work the same way.

Code example:

Bad:

class NotificationService {
    public void send(String message, String type) {
        if (type.equals("Email")) { /* send email */ }
        else if (type.equals("SMS")) { /* send SMS */ }
    }
}
Enter fullscreen mode Exit fullscreen mode

Good:

interface NotificationSender {
    void send(String message);
}

class EmailSender implements NotificationSender {
    public void send(String message) { /* send email */ }
}

class SMSSender implements NotificationSender {
    public void send(String message) { /* send SMS */ }
}

class NotificationService {
    private NotificationSender sender;

    public NotificationService(NotificationSender sender) {
        this.sender = sender;
    }

    public void notify(String message) {
        sender.send(message);
    }
}
Enter fullscreen mode Exit fullscreen mode

New types? Just add a new class. Your core logic stays untouched, stress levels stay low.


🧠 L — Liskov Substitution Principle (LSP)

"Objects of a superclass should be replaceable with objects of its subclasses without affecting correctness."

Real-world analogy:
If you rent a car, you expect to drive it — whether it’s a sedan, SUV, or convertible. If the rental company handed you a boat with wheels, you'd be furious.

Code example:

Bad:

class Bird {
    public void fly() { /* flying logic */ }
}

class Penguin extends Bird {
    public void fly() { 
        throw new UnsupportedOperationException("Penguins can't fly!");
    }
}
Enter fullscreen mode Exit fullscreen mode

Better:

interface Bird {
    void eat();
}

interface FlyingBird extends Bird {
    void fly();
}

class Sparrow implements FlyingBird {
    public void eat() { /* eat */ }
    public void fly() { /* fly */ }
}

class Penguin implements Bird {
    public void eat() { /* eat */ }
}
Enter fullscreen mode Exit fullscreen mode

Now penguins aren’t pretending to be something they’re not. Less deception, fewer exceptions.


🧠 I — Interface Segregation Principle (ISP)

"No client should be forced to depend on methods it does not use."

Real-world analogy:
Ordering a coffee and getting a full 10-course meal, whether you asked for it or not.

Code example:

Bad:

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

class Robot implements Worker {
    public void work() { /* working... */ }
    public void eat() { /* ??? Robots don't eat! */ }
}
Enter fullscreen mode Exit fullscreen mode

Better:

interface Workable {
    void work();
}

interface Eatable {
    void eat();
}

class Human implements Workable, Eatable {
    public void work() { /* work */ }
    public void eat() { /* eat */ }
}

class Robot implements Workable {
    public void work() { /* work */ }
}
Enter fullscreen mode Exit fullscreen mode

Now each class only implements what it actually needs. Simple. Clean. Logical.


🧠 D — Dependency Inversion Principle (DIP)

"Depend on abstractions, not on concretions."

Real-world analogy:
If your smartphone was hard-wired to only work with one charger brand, you’d riot. Thankfully, it uses USB or wireless — an abstraction.

Code example:

Bad:

class MySQLDatabase {
    public void connect() { /* ... */ }
}

class UserRepository {
    private MySQLDatabase db = new MySQLDatabase();

    public void saveUser() { db.connect(); /* save logic */ }
}
Enter fullscreen mode Exit fullscreen mode

Good:

interface Database {
    void connect();
}

class MySQLDatabase implements Database {
    public void connect() { /* ... */ }
}

class UserRepository {
    private Database db;

    public UserRepository(Database db) {
        this.db = db;
    }

    public void saveUser() { db.connect(); /* save logic */ }
}
Enter fullscreen mode Exit fullscreen mode

Now you can swap databases like changing socks. Dependency injection = freedom.


💡 Wrapping Up

SOLID principles are like traffic rules for your code. They won’t stop you from writing spaghetti, but they’ll give you the map to a much safer, maintainable, and scalable design.

If you start noticing that:

  • your classes are doing too many things,

  • adding a new feature breaks four others,

  • or your code makes you want to fake your own death and start a new identity...

...chances are, you're breaking one (or more) SOLID principles.

Stick to them and your future self — and your teammates — will silently thank you.

DevCycle image

OpenFeature Multi-Provider: Enabling New Feature Flagging Use-Cases

DevCycle is the first feature management platform with OpenFeature built in. We pair the reliability, scalability, and security of a managed service with freedom from vendor lock-in, helping developers ship faster with true OpenFeature-native feature flagging.

Watch Full Video 🎥

Top comments (0)

Tiger Data image

🐯 🚀 Timescale is now TigerData: Building the Modern PostgreSQL for the Analytical and Agentic Era

We’ve quietly evolved from a time-series database into the modern PostgreSQL for today’s and tomorrow’s computing, built for performance, scale, and the agentic future.

So we’re changing our name: from Timescale to TigerData. Not to change who we are, but to reflect who we’ve become. TigerData is bold, fast, and built to power the next era of software.

Read more

👋 Kindness is contagious

If this **helped, please leave a ❤️ or a friendly comment!

Okay