SOLID Principles

KishanKishan
4 min read

This post is a personal reference for revising SOLID principles — written in a natural tone, with quick explanations and Java examples.


🔹 1. Single Responsibility Principle (SRP)

Every class should have one responsibility — a single reason to change. Helps in identifying when to break classes.

👉 “One responsibility” = one reason to change.

// SRP violation
class InvoiceManager {
    void calculateTotal() { ... }
    void printInvoice() { ... } // printing shouldn't live here
}

// SRP applied
class InvoiceCalculator {
    void calculateTotal() { ... }
}

class InvoicePrinter {
    void printInvoice() { ... }
}

🔹 2. Open/Closed Principle (OCP)

Every class should be open to extension, but closed to modification.
We should not be touching existing ones.
Usually, violations are fixed by Factory/Strategy patterns etc., or polymorphism.

// OCP violation
class NotificationService {
    void send(String type) {
        if (type.equals("email")) { ... }
        else if (type.equals("sms")) { ... }
    }
}

// OCP applied
interface Notifier {
    void send();
}

class EmailNotifier implements Notifier {
    public void send() { ... }
}

class SMSNotifier implements Notifier {
    public void send() { ... }
}

🔹 3. Liskov Substitution Principle (LSP)

All the child classes should be able to replace their parent class.
It helps to identify correct inheritance patterns.
A real-world is-a relation doesn’t always translate to an inheritance in OOD.

👉 Hint to identify – Tell, don’t ask (if you're checking types or behavior before calling a method, probably a violation)

❌ Violation example:

class Bird {
    void fly() { System.out.println("Flying..."); }
}

class Ostrich extends Bird {
    @Override
    void fly() {
        throw new UnsupportedOperationException("Ostrich can't fly!");
    }
}

✅ LSP applied:

interface Bird { }

interface FlyingBird extends Bird {
    void fly();
}

class Sparrow implements FlyingBird {
    public void fly() {
        System.out.println("Sparrow flying");
    }
}

class Ostrich implements Bird {
    // no fly method
}

🔹 4. Interface Segregation Principle (ISP)

Pretty similar to SRP, each interface should be separated based on responsibility.
Helps in deciding right abstraction.
Find fat interfaces, and break them down.

// ISP violation
interface MultiFunctionDevice {
    void print();
    void scan();
    void fax();
}

class BasicPrinter implements MultiFunctionDevice {
    public void print() { ... }
    public void scan() { ... }
    public void fax() { throw new UnsupportedOperationException(); }
}

// ISP applied
interface Printer {
    void print();
}

interface Scanner {
    void scan();
}

🔹 5. Dependency Inversion Principle (DIP)

The high-level modules should not depend on low-level modules — instead, both should depend on abstractions.
So, the dependency from high → low level module inverts to low → abstraction.

Some other concepts:

  • Dependency Injection: the class should not create its own dependency — it should get it from downstream.
  • Inversion of Control: If we follow the dependency injection pattern, then at the main method, the code becomes messy and difficult to manage. So we delegate object creation to some framework (like Spring IoC container).
// DIP violation
class EmailService {
    void send(String msg) { ... }
}

class NotificationManager {
    EmailService service = new EmailService();
    void notifyUser(String msg) {
        service.send(msg);
    }
}

// DIP applied
interface MessageService {
    void send(String msg);
}

class EmailService implements MessageService {
    public void send(String msg) { ... }
}

class NotificationManager {
    private final MessageService service;

    NotificationManager(MessageService service) {
        this.service = service;
    }

    void notifyUser(String msg) {
        service.send(msg);
    }
}

🔁 Final Thoughts

  • SRP and OCP sort of follow each other. If a class is created with limited and well-defined responsibility, it's rare that we touch it later — because it’s already well implemented.
  • Also, Dependency Inversion is only possible if LSP and ISP are followed well.
  • These principles are not meant to be applied rigidly — the goal is to build systems that are easier to change, test, and scale.

📝 This post was written for personal revision, but feel free to adapt or share it if you find it useful.

0
Subscribe to my newsletter

Read articles from Kishan directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Kishan
Kishan