SOLID Design Principle


SOLID principles are a fundamental set of concepts in object-oriented programming (OOP) that aim to make software designs more understandable, flexible, and maintainable. The principles were introduced by Robert C. Martin (also known as Uncle Bob) and are represented by the acronym SOLID:

What are SOLID Principles?

SOLID is an acronym that stands for:

  1. S – Single Responsibility Principle (SRP)
  2. O – Open/Closed Principle (OCP)
  3. L – Liskov Substitution Principle (LSP)
  4. I – Interface Segregation Principle (ISP)
  5. D – Dependency Inversion Principle (DIP)
SOLID Design Principle

1. Single Responsibility Principle (SRP)

SRP suggests that a class should have only one reason to change, meaning it should have a single responsibility. For example:

// Bad example violating SRP
class User {
    public void createUser() {
        // Code for creating a user
    }
    
    public void sendEmail() {
        // Code for sending email
    }
}

In the above code, the User class handles both user creation and email sending. A better approach follows:

// Improved example following SRP
class UserManager {
    public void createUser() {
        // Code for creating a user
    }
}

class EmailService {
    public void sendEmail() {
        // Code for sending email
    }
}

2. Open/Closed Principle (OCP)

The OCP states that classes should be open for extension but closed for modification. Here’s an example:

// Violating OCP
class Rectangle {
    public double calculateArea() {
        // Calculation logic for rectangle area
    }
}

class Circle {
    public double calculateArea() {
        // Calculation logic for circle area
    }
}

To adhere to OCP, use abstraction and inheritance:

// Following OCP
interface Shape {
    double calculateArea();
}

class Rectangle implements Shape {
    public double calculateArea() {
        // Calculation logic for rectangle area
    }
}

class Circle implements Shape {
    public double calculateArea() {
        // Calculation logic for circle area
    }
}

3. Liskov Substitution Principle (LSP)

LSP emphasizes that objects of a superclass should be replaceable with objects of its subclasses without affecting the correctness of the program. Consider this example:

// Violating LSP
class Bird {
    public void fly() {
        // Logic for flying
    }
}

class Ostrich extends Bird {
    // Ostriches cannot fly
}

A better design preserving LSP:

// Following LSP
interface Bird {
    void fly();
}

class Sparrow implements Bird {
    public void fly() {
        // Logic for flying
    }
}

class Ostrich implements Bird {
    public void fly() {
        // Ostriches cannot fly
        throw new UnsupportedOperationException("Ostrich cannot fly");
    }
}

4. Interface Segregation Principle (ISP)

ISP suggests that no client should be forced to depend on methods it does not use. Consider this scenario:

// Violating ISP
interface Worker {
    void work();
    void eat();
}

class Robot implements Worker {
    public void work() {
        // Robot working
    }
    
    public void eat() {
        // Robots don't eat, violating ISP
    }
}

Following ISP:

// Following ISP
interface Workable {
    void work();
}

interface Eatable {
    void eat();
}

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

5. Dependency Inversion Principle (DIP)

DIP emphasizes that high-level modules/classes should not depend on low-level modules/classes but both should depend on abstractions. Consider this example:

// Violating DIP
class LightBulb {
    public void turnOn() {
        // Logic for turning on the bulb
    }
}

class Switch {
    private LightBulb bulb;

    public Switch() {
        this.bulb = new LightBulb(); // Direct dependency
    }

    public void flip() {
        bulb.turnOn();
    }
}

Following DIP:

// Following DIP
interface Switchable {
    void turnOn();
}

class LightBulb implements Switchable {
    public void turnOn() {
        // Logic for turning on the bulb
    }
}

class Switch {
    private Switchable device;

    public Switch(Switchable device) {
        this.device = device; // Depend on abstraction
    }

    public void flip() {
        device.turnOn();
    }
}

Advantages of SOLID Principles

  • Maintainability: Code adhering to SOLID principles tends to be easier to maintain because it’s modular, less tightly coupled, and easier to understand.
  • Flexibility: The principles promote flexibility by allowing easier changes, extensions, and adaptations without affecting the entire codebase.
  • Readability: Code following these principles tends to be more readable and understandable, making it easier for developers to collaborate, maintain, and debug.
  • Scalability: The modular and decoupled nature of SOLID code makes it more scalable, allowing for easier integration of new features or modules.

Also see: Introduction to Business Process Model and Notation (BPMN)

Summary

SOLID principles leads to cleaner, more maintainable code and facilitates easier changes and extensions in the future. Integrating these principles in your Java projects can significantly improve their design and flexibility.

References

  1. Software design pattern – Wikipedia
  2. SOLID – Wikipedia

Similar Posts

About the Author

Atul Rai
I love sharing my experiments and ideas with everyone by writing articles on the latest technological trends. Read all published posts by Atul Rai.