DEV Community

Tommy
Tommy

Posted on

SOLID - Software Design Principles

SOLID

is an acronym representing five design principles in software development: Single Responsibility, Open/Closed, Liskov Substitution, Interface Segregation, and Dependency Inversion. These principles aim to create more maintainable, scalable, and understandable software by promoting modular and flexible design.

Let's understand each of them with examples.

Single Responsibility Principle

states that a class should have only one reason to change, meaning it should have only one responsibility or job. In the example below, the Calculator class is responsible for performing arithmetic operations, while the Printer class is responsible for printing results. Each class has a single responsibility, making the code easier to understand and maintain.

public class Calculator {
    public int add(int a, int b) {
        return a + b;
    }
}

public class Printer {
    public void printResult(int result) {
        System.out.println("Result: " + result);
    }
}
Enter fullscreen mode Exit fullscreen mode

Open/Closed Principle

states that software entities (classes, modules, functions, etc.) should be open for extension but closed for modification. In this example, the Shape class is open for extension as new shapes can be added by creating new subclasses like Circle and Rectangle, but it is closed for modification as existing classes don't need to be altered when new shapes are introduced.

public abstract class Shape {
    public abstract double area();
}

public class Circle extends Shape {
    private double radius;

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

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

public class Rectangle extends Shape {
    private double width;
    private double height;

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

    public double area() {
        return width * height;
    }
}
Enter fullscreen mode Exit fullscreen mode

Liskov Substitution Principle

states that objects of a superclass should be replaceable with objects of its subclasses without affecting the correctness of the program. In this example, both Duck and Sparrow are subclasses of Bird, and they override the fly() method according to their own behavior. When substituted for a Bird object, they retain the expected behavior without breaking the program.

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

class Duck extends Bird {
    @Override
    public void fly() {
        System.out.println("Flying like a duck");
    }
}

class Sparrow extends Bird {
    @Override
    public void fly() {
        System.out.println("Flying like a sparrow");
    }
}

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

        duck.fly(); // Output: Flying like a duck
        sparrow.fly(); // Output: Flying like a sparrow
    }
}

Enter fullscreen mode Exit fullscreen mode

Interface Segregation Principle

states that clients should not be forced to depend on interfaces they do not use. In other words, it suggests that interfaces should be specific to the needs of the clients. In this example, the Printer and Scanner interfaces segregate the behaviors related to printing and scanning respectively. The AllInOnePrinter class implements both interfaces, but other classes can choose to implement only the interface(s) relevant to them.

interface Printer {
    void print();
}

interface Scanner {
    void scan();
}

class AllInOnePrinter implements Printer, Scanner {
    public void print() {
        System.out.println("Printing...");
    }

    public void scan() {
        System.out.println("Scanning...");
    }
}

Enter fullscreen mode Exit fullscreen mode

Dependency Inversion Principle

suggests that high-level modules should not depend on low-level modules but rather both should depend on abstractions. Additionally, it advocates for the use of interfaces or abstract classes to decouple classes and promote flexibility. In this example, the MessageSender class depends on the Message interface rather than concrete implementations like Email or SMS, allowing for easier swapping of message types without modifying the MessageSender class.

interface Message {
    String getContent();
}

class Email implements Message {
    public String getContent() {
        return "This is an email message";
    }
}

class SMS implements Message {
    public String getContent() {
        return "This is an SMS message";
    }
}

class MessageSender {
    private Message message;

    public MessageSender(Message message) {
        this.message = message;
    }

    public void sendMessage() {
        System.out.println("Sending message: " + message.getContent());
    }
}

Enter fullscreen mode Exit fullscreen mode

Top comments (0)