SOLID principles

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.

Leave a comment