The SOLID principles help in writing clean, scalable, and maintainable code, and they are highly applicable in Java as well. Let’s go through each principle with examples in Java.


1. Single Responsibility Principle (SRP)

A class should have only one reason to change, meaning it should have only one responsibility.

Example:

javaCopy code// BAD: Violates SRP because the User class handles both user data and database operations
class User {
    private String username;
    private String password;

    public User(String username, String password) {
        this.username = username;
        this.password = password;
    }

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

// GOOD: Each class has a single responsibility
class User {
    private String username;
    private String password;

    public User(String username, String password) {
        this.username = username;
        this.password = password;
    }

    // Getters and Setters
}

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

Here, User handles user data, and UserRepository handles database operations.


2. Open/Closed Principle (OCP)

Software entities (classes, modules, functions, etc.) should be open for extension but closed for modification.

Example:

javaCopy code// BAD: Violates OCP because modifying this class requires changing existing code
class AreaCalculator {
    public double calculateRectangleArea(Rectangle rectangle) {
        return rectangle.getHeight() * rectangle.getWidth();
    }

    public double calculateCircleArea(Circle circle) {
        return Math.PI * circle.getRadius() * circle.getRadius();
    }
}

// GOOD: Follows OCP by using an interface for shapes
interface Shape {
    double calculateArea();
}

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

    public Rectangle(double height, double width) {
        this.height = height;
        this.width = width;
    }

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

class Circle implements Shape {
    private double radius;

    public Circle(double radius) {
        this.radius = radius;
    }

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

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

Here, you can extend the functionality by adding new shapes without modifying the existing AreaCalculator class.


3. Liskov Substitution Principle (LSP)

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

Example:

javaCopy code// BAD: Violates LSP because Square's setWidth and setHeight methods don't behave correctly
class Rectangle {
    protected double height;
    protected double width;

    public void setHeight(double height) {
        this.height = height;
    }

    public void setWidth(double width) {
        this.width = width;
    }

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

class Square extends Rectangle {
    @Override
    public void setHeight(double height) {
        this.height = height;
        this.width = height;  // A square must have equal width and height
    }

    @Override
    public void setWidth(double width) {
        this.width = width;
        this.height = width;  // A square must have equal width and height
    }
}

// GOOD: Avoid forcing a square into the rectangle structure
interface Shape {
    double calculateArea();
}

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

    public Rectangle(double height, double width) {
        this.height = height;
        this.width = width;
    }

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

class Square implements Shape {
    private double side;

    public Square(double side) {
        this.side = side;
    }

    public double calculateArea() {
        return side * side;
    }
}

Here, Square and Rectangle both implement the Shape interface correctly without violating the principle.


4. Interface Segregation Principle (ISP)

Clients should not be forced to depend on methods they do not use.

Example:

javaCopy code// BAD: A large interface that forces implementation of unnecessary methods
interface Worker {
    void work();
    void eat();
}

class Employee implements Worker {
    public void work() {
        System.out.println("Employee working");
    }

    public void eat() {
        System.out.println("Employee eating");
    }
}

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

    // Robot doesn't eat, so this method is unnecessary
    public void eat() {
        throw new UnsupportedOperationException("Robots don't eat");
    }
}

// GOOD: Split the interfaces into smaller ones
interface Workable {
    void work();
}

interface Eatable {
    void eat();
}

class Employee implements Workable, Eatable {
    public void work() {
        System.out.println("Employee working");
    }

    public void eat() {
        System.out.println("Employee eating");
    }
}

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

Here, Robot does not need to implement the eat() method because it only implements the Workable interface.


5. Dependency Inversion Principle (DIP)

High-level modules should not depend on low-level modules. Both should depend on abstractions.

Example:

// BAD: High-level class directly depends on a low-level class
class Keyboard {
// Implementation details
}

class Computer {
private Keyboard keyboard;

public Computer() {
    this.keyboard = new Keyboard(); // Dependency on low-level module
}

}

// GOOD: Use interfaces to depend on abstractions
interface InputDevice {
void input();
}

class Keyboard implements InputDevice {
public void input() {
System.out.println(“Keyboard input”);
}
}

class Mouse implements InputDevice {
public void input() {
System.out.println(“Mouse input”);
}
}

class Computer {
private InputDevice inputDevice;

public Computer(InputDevice inputDevice) {
    this.inputDevice = inputDevice; // Depends on abstraction
}

public void getInput() {
    inputDevice.input();
}

}

Now the Computer class depends on the InputDevice abstraction rather than a specific implementation. This allows flexibility to switch between different input devices without changing the Computer class.

These examples show how the SOLID principles help in designing systems that are more modular, flexible, and easier to maintain.

Thank you.

Leave A Comment

Recommended Posts

Tech Post

Capture Images & Video in the Flutter App

Add Dependencies First, add the image_picker package to your pubspec.yaml file: dependencies:flutter:sdk: flutterimage_picker: ^0.8.7+4 Then, run flutter pub get to install the package. Update Android and iOS Configuration Android In your android/app/src/main/AndroidManifest.xml, add the following permissions: <uses-permission android:name=”android.permission.CAMERA”/> <uses-permission android:name=”android.permission.WRITE_EXTERNAL_STORAGE”/> <uses-permission android:name=”android.permission.READ_EXTERNAL_STORAGE”/> […]

coding-studio.com