SOLID Principles in Java with Examples

Introduction

The SOLID principles are a set of five design principles in object-oriented programming that help developers create more maintainable, understandable, and flexible software. These principles were introduced by Robert C. Martin, also known as Uncle Bob. SOLID stands for:

  1. Single Responsibility Principle (SRP)
  2. Open/Closed Principle (OCP)
  3. Liskov Substitution Principle (LSP)
  4. Interface Segregation Principle (ISP)
  5. Dependency Inversion Principle (DIP)

Table of Contents

  1. Single Responsibility Principle (SRP)
  2. Open/Closed Principle (OCP)
  3. Liskov Substitution Principle (LSP)
  4. Interface Segregation Principle (ISP)
  5. Dependency Inversion Principle (DIP)
SOLID Principles in Java with Examples

1. Single Responsibility Principle (SRP)

The Single Responsibility Principle states that a class should have only one reason to change, meaning it should have only one responsibility.

Example:

Violation of SRP:

class User {
    private String name;
    private String email;

    public User(String name, String email) {
        this.name = name;
        this.email = email;
    }

    public void saveUser() {
        // Code to save user to database
        System.out.println("User saved to database.");
    }

    public void sendEmail(String message) {
        // Code to send email
        System.out.println("Email sent to " + email + " with message: " + message);
    }

    // Getters and setters
}

Adherence to SRP:

class User {
    private String name;
    private String email;

    public User(String name, String email) {
        this.name = name;
        this.email = email;
    }

    // Getters and setters
}

class UserRepository {
    public void saveUser(User user) {
        // Code to save user to database
        System.out.println("User " + user.getName() + " saved to database.");
    }
}

class EmailService {
    public void sendEmail(User user, String message) {
        // Code to send email
        System.out.println("Email sent to " + user.getEmail() + " with message: " + message);
    }
}

Main class:

public class Main {
    public static void main(String[] args) {
        User user = new User("John Doe", "[email protected]");

        UserRepository userRepository = new UserRepository();
        userRepository.saveUser(user);

        EmailService emailService = new EmailService();
        emailService.sendEmail(user, "Welcome to our platform!");
    }
}

2. Open/Closed Principle (OCP)

The Open/Closed Principle states that software entities should be open for extension but closed for modification.

Example:

Violation of OCP:

class Rectangle {
    private double width;
    private double height;

    // Getters and setters
}

class Circle {
    private double radius;

    // Getters and setters
}

class AreaCalculator {
    public double calculateArea(Object shape) {
        if (shape instanceof Rectangle) {
            Rectangle rectangle = (Rectangle) shape;
            return rectangle.getWidth() * rectangle.getHeight();
        } else if (shape instanceof Circle) {
            Circle circle = (Circle) shape;
            return Math.PI * circle.getRadius() * circle.getRadius();
        }
        return 0;
    }
}

Adherence to OCP:

interface Shape {
    double calculateArea();
}

class Rectangle implements Shape {
    private double width;
    private double height;

    @Override
    public double calculateArea() {
        return width * height;
    }

    // Getters and setters
}

class Circle implements Shape {
    private double radius;

    @Override
    public double calculateArea() {
        return Math.PI * radius * radius;
    }

    // Getters and setters
}

class AreaCalculator {
    public double calculateArea(Shape shape) {
        return shape.calculateArea();
    }
}

Main class:

public class Main {
    public static void main(String[] args) {
        Shape rectangle = new Rectangle(4, 5);
        Shape circle = new Circle(3);

        AreaCalculator calculator = new AreaCalculator();
        System.out.println("Rectangle area: " + calculator.calculateArea(rectangle));
        System.out.println("Circle area: " + calculator.calculateArea(circle));
    }
}

3. Liskov Substitution Principle (LSP)

The Liskov Substitution Principle states that objects of a superclass should be replaceable with objects of a subclass without affecting the correctness of the program.

Example:

Violation of LSP:

class Bird {
    public void fly() {
        System.out.println("Bird is flying");
    }
}

class Sparrow extends Bird {
    @Override
    public void fly() {
        System.out.println("Sparrow is flying");
    }
}

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

Adherence to LSP:

abstract class Bird {
    public abstract void move();
}

class Sparrow extends Bird {
    @Override
    public void move() {
        System.out.println("Sparrow is flying");
    }
}

class Penguin extends Bird {
    @Override
    public void move() {
        System.out.println("Penguin is swimming");
    }
}

public class Main {
    public static void main(String[] args) {
        Bird sparrow = new Sparrow();
        Bird penguin = new Penguin();

        sparrow.move(); // Output: Sparrow is flying
        penguin.move(); // Output: Penguin is swimming
    }
}

4. Interface Segregation Principle (ISP)

The Interface Segregation Principle states that no client should be forced to depend on methods it does not use. Instead of one large interface, many smaller and more specific interfaces are preferred.

Example:

Violation of ISP:

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

class Developer implements Worker {
    @Override
    public void work() {
        System.out.println("Developer is coding.");
    }

    @Override
    public void eat() {
        System.out.println("Developer is eating.");
    }
}

class Robot implements Worker {
    @Override
    public void work() {
        System.out.println("Robot is working.");
    }

    @Override
    public void eat() {
        throw new UnsupportedOperationException("Robot does not eat.");
    }
}

Adherence to ISP:

interface Workable {
    void work();
}

interface Eatable {
    void eat();
}

class Developer implements Workable, Eatable {
    @Override
    public void work() {
        System.out.println("Developer is coding.");
    }

    @Override
    public void eat() {
        System.out.println("Developer is eating.");
    }
}

class Robot implements Workable {
    @Override
    public void work() {
        System.out.println("Robot is working.");
    }
}

Main class:

public class Main {
    public static void main(String[] args) {
        Workable developer = new Developer();
        developer.work();

        Eatable eatableDeveloper = (Eatable) developer;
        eatableDeveloper.eat();

        Workable robot = new Robot();
        robot.work();
    }
}

5. Dependency Inversion Principle (DIP)

The Dependency Inversion Principle states that high-level modules should not depend on low-level modules. Both should depend on abstractions.

Example:

Violation of DIP:

class LightBulb {
    public void turnOn() {
        System.out.println("LightBulb is turned on");
    }

    public void turnOff() {
        System.out.println("LightBulb is turned off");
    }
}

class Switch {
    private LightBulb lightBulb;

    public Switch(LightBulb lightBulb) {
        this.lightBulb = lightBulb;
    }

    public void flip(boolean on) {
        if (on) {
            lightBulb.turnOn();
        } else {
            lightBulb.turnOff();
        }
    }
}

Adherence to DIP:

interface Switchable {
    void turnOn();
    void turnOff();
}

class LightBulb implements Switchable {
    @Override
    public void turnOn() {
        System.out.println("LightBulb is turned on");
    }

    @Override
    public void turnOff() {
        System.out.println("LightBulb is turned off");
    }
}

class Switch {
    private Switchable switchable;

    public Switch(Switchable switchable) {
        this.switchable = switchable;
    }

    public void flip(boolean on) {
        if (on) {
            switchable.turnOn();
        } else {
            switchable.turnOff();
        }
    }
}

Main class:

public class Main {
    public static void main(String[] args) {
        Switchable lightBulb = new LightBulb();
        Switch lightSwitch = new Switch(lightBulb);
        lightSwitch.flip(true);  // Output: LightBulb is turned on
        lightSwitch.flip(false); // Output: LightBulb is turned off
    }
}

Conclusion

The SOLID principles are fundamental guidelines for designing robust, maintainable, and flexible object-oriented software. By adhering to these principles, developers can create code that is easier to understand, modify, and extend, leading to more sustainable software development practices. Here

’s a quick recap of the SOLID principles:

  1. Single Responsibility Principle (SRP): A class should have only one reason to change.
  2. Open/Closed Principle (OCP): Software entities should be open for extension but closed for modification.
  3. Liskov Substitution Principle (LSP): Objects of a superclass should be replaceable with objects of a subclass without affecting the correctness of the program.
  4. Interface Segregation Principle (ISP): No client should be forced to depend on methods it does not use.
  5. Dependency Inversion Principle (DIP): High-level modules should not depend on low-level modules. Both should depend on abstractions.

Happy coding!

Comments