SOLID is an acronym that represents a set of five design principles in object-oriented programming and software design. These principles aim to create more maintainable, flexible, and scalable software by promoting a modular and clean code structure. The SOLID principles were introduced by Robert C. Martin and have become widely adopted in the software development industry. Here’s a brief overview of each principle:

Single Responsibility Principle (SRP):

  • A class should have only one reason to change, meaning that it should have only one responsibility or job.
  • This principle encourages the separation of concerns and helps to ensure that a class is focused on doing one thing well.
// Before SRP
class Report {
    public void generateReport() {
        // code for generating the report
    }

    public void saveToFile() {
        // code for saving the report to a file
    }
}

// After SRP
class Report {
    public void generateReport() {
        // code for generating the report
    }
}

class ReportSaver {
    public void saveToFile(Report report) {
        // code for saving the report to a file
    }
}

Open/Closed Principle (OCP):

  • Software entities (classes, modules, functions, etc.) should be open for extension but closed for modification.
  • This encourages developers to add new functionality through the creation of new classes or modules rather than altering existing ones.
// Before OCP
class Rectangle {
    public double width;
    public double height;
}

class AreaCalculator {
    public double calculateArea(Rectangle rectangle) {
        return rectangle.width * rectangle.height;
    }
}

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

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

    // constructor and other methods

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

class Circle implements Shape {
    private double radius;

    // constructor and other methods

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

Liskov Substitution Principle (LSP):

  • Subtypes should be substitutable for their base types without altering the correctness of the program.
  • This principle ensures that objects of a derived class can be used in place of objects of the base class without affecting the program’s functionality.
// Before LSP
class Bird {
    public void fly() {
        // code for flying
    }
}

class Ostrich extends Bird {
    // Ostrich is a bird, but it can't fly
}

// After LSP
interface FlyingBird {
    void fly();
}

class Sparrow implements FlyingBird {
    @Override
    public void fly() {
        // code for flying
    }
}

class Ostrich {
    // Ostrich doesn't implement FlyingBird, as it can't fly
}

Interface Segregation Principle (ISP):

  • Clients should not be forced to depend on interfaces they do not use.
  • It advocates for the creation of small, specific interfaces rather than large, general-purpose ones, to avoid clients being forced to implement methods they don’t need.
// Before ISP
interface Worker {
    void work();

    void eat();

    void sleep();
}

class Engineer implements Worker {
    @Override
    public void work() {
        // code for working
    }

    @Override
    public void eat() {
        // code for eating
    }

    @Override
    public void sleep() {
        // code for sleeping
    }
}

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

interface Eatable {
    void eat();
}

interface Sleepable {
    void sleep();
}

class Engineer implements Workable, Eatable, Sleepable {
    @Override
    public void work() {
        // code for working
    }

    @Override
    public void eat() {
        // code for eating
    }

    @Override
    public void sleep() {
        // code for sleeping
    }
}

Dependency Inversion Principle (DIP):

  • High-level modules should not depend on low-level modules. Both should depend on abstractions.
  • Abstractions should not depend on details; details should depend on abstractions.
  • This principle promotes the use of abstractions (like interfaces or abstract classes) to decouple high-level and low-level modules, making the system more flexible and easier to change.
// Before DIP
class LightBulb {
    public void turnOn() {
        // code for turning on the light bulb
    }

    public void turnOff() {
        // code for turning off the light bulb
    }
}

class Switch {
    private LightBulb bulb;

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

    public void operate() {
        // code for operating the switch
        if (/* some condition */) {
            bulb.turnOn();
        } else {
            bulb.turnOff();
        }
    }
}

// After DIP
interface Switchable {
    void turnOn();

    void turnOff();
}

class LightBulb implements Switchable {
    @Override
    public void turnOn() {
        // code for turning on the light bulb
    }

    @Override
    public void turnOff() {
        // code for turning off the light bulb
    }
}

class Switch {
    private Switchable device;

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

    public void operate() {
        // code for operating the switch
        if (/* some condition */) {
            device.turnOn();
        } else {
            device.turnOff();
        }
    }
}

Adhering to SOLID principles can result in code that is easier to understand, maintain, and extend. These principles contribute to the overall goal of creating robust and scalable software systems.