Why SOLID Matters

1. Single Responsibility Principle (SRP)

❌ Bad Example — Violates SRP

class UserService {
    void register(User user) {
        validate(user);
        saveToDB(user);
        sendWelcomeEmail(user);
    }

    void validate(User user) { }
    void saveToDB(User user) { }
    void sendWelcomeEmail(User user) { }
}        

This class has multiple responsibilities → validation, persistence, and email sending.

✔ Good Example — Follows SRP

class UserValidator {
    void validate(User user) { }
}

class UserRepository {
    void save(User user) { }
}

class EmailService {
    void sendWelcomeEmail(User user) { }
}

class UserService {
    private final UserValidator validator = new UserValidator();
    private final UserRepository repository = new UserRepository();
    private final EmailService emailService = new EmailService();

    void register(User user) {
        validator.validate(user);
        repository.save(user);
        emailService.sendWelcomeEmail(user);
    }
}        

Each class now has one responsibility.


2. Open/Closed Principle (OCP)

❌ Bad Example — Requires modifying code for each new payment type

class PaymentService {
    void pay(String type) {
        if (type.equals("credit")) {
            // pay with credit card
        } else if (type.equals("paypal")) {
            // pay with PayPal
        }
    }
}        

Adding ApplePay means adding another if → violation of OCP.

✔ Good Example — Open for extension, closed for modification

interface PaymentMethod {
    void pay();
}

class CreditCardPayment implements PaymentMethod {
    public void pay() { System.out.println("Pay with credit card"); }
}

class PayPalPayment implements PaymentMethod {
    public void pay() { System.out.println("Pay with PayPal"); }
}

class PaymentService {
    private final PaymentMethod method;

    PaymentService(PaymentMethod method) {
        this.method = method;
    }

    void pay() {
        method.pay();
    }
}        

To add ApplePay, you only create a new class — no old code changes.


3. Liskov Substitution Principle (LSP)

❌ Bad Example — Violates LSP

class Bird {
    void fly() { }
}

class Penguin extends Bird {
    @Override
    void fly() {
        throw new RuntimeException("Penguins can't fly");
    }
}        

A Penguin cannot be substituted for a Bird that is expected to fly.

✔ Good Example — Proper abstraction

class Bird { }

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

class Eagle extends FlyingBird {
    @Override
    void fly() { System.out.println("Eagle flying"); }
}

class Penguin extends Bird {
    void swim() { System.out.println("Penguin swimming"); }
}        

Subclasses now behave logically.


4. Interface Segregation Principle (ISP)

❌ Bad Example — Too large interface

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

class Robot implements Worker {
    public void work() { }
    public void eat() { }     // meaningless
    public void sleep() { }   // meaningless
}        

Robot is forced to implement irrelevant methods.

✔ Good Example — Split into smaller interfaces

interface Workable { void work(); }
interface Eatable { void eat(); }
interface Sleepable { void sleep(); }

class Human implements Workable, Eatable, Sleepable {
    public void work() { }
    public void eat() { }
    public void sleep() { }
}

class Robot implements Workable {
    public void work() { }
}        

Each class implements only what it needs.


5. Dependency Inversion Principle (DIP)

❌ Bad Example — High-level depends on low-level

class EmailSender {
    void send(String msg) { }
}

class Notification {
    private EmailSender email = new EmailSender();

    void send(String msg) {
        email.send(msg);
    }
}        

✔ Good Example — Depend on abstraction

interface MessageSender {
    void send(String msg);
}

class EmailSender implements MessageSender {
    public void send(String msg) { }
}

class SmsSender implements MessageSender {
    public void send(String msg) { }
}

class Notification {
    private final MessageSender sender;

    Notification(MessageSender sender) {
        this.sender = sender;
    }

    void send(String msg) {
        sender.send(msg);
    }
}        

The high-level module (Notification) now depends only on an interface.

To view or add a comment, sign in

More articles by mahmoud abbasi

Explore content categories