Let’s talk about SOLID acronym.

SOLID is a mnemonic acronym with 5 design principles as guidelines to follow when we start developing new software.

They were created by Robert C. Martin. I will use pictures created by Ugonna Thelma in this post, to help us to understand each one:

Single-Responsibility Principle

Each class, component, entity, module or function should have only one responsibility. In other words, a class should be responsible for only one specific aspect of the system.

Created by Ugonna Thelma

The robot on the left side has a lot of responsibilities. This robot is chef, gardener, painter and driver. However, these responsibilities should be handled by separate robots. Each responsibility of your system should have an entity that knows how to do only what it specialises in, and is, in fact, the best at that specific task.

This makes the system easier to maintain, as changes in one area do not affect unrelated functionalities.

Key points:

  1. Reuse the code
  2. Refactoring
  3. Automated testes
  4. Fewer bugs (As soon as we detect a bug, we can isolate and resolve the issue).

Open-Closed Principle

Software entities, such as classes, entities, modules, or functions should be open for extension but closed for modification.

Created by Ugonna Thelma

If you need to add a new task as a painter, you should not have to modify the class removing the task as a cutter. Instead, you should be able to extend the system by creating a new class that implements the new functionality.

Wait a minute! The Open-Closed Principle above, violates the Single Responsibility Principle, because we added a new responsibility to the robot. 🙄

Liskov Substitution Principle

Subtypes must be substitutable for their base types without altering any of the properties of that program. This means a subclass should be able to replace its superclass without unexpected behaviour.

If you have a robot (Sam) with a method – makeCoffee(), and a subclass Eden, the Eden robot should be able to replace Sam without causing unexpected behaviour.

When the same actions as the parent class cannot be performed by a child class, bugs can be caused.

When you have a class (Sam) and create another class from it (Eden), the original class becomes the parent, and the new class becomes the child. The child class should be able to perform everything the parent class can. This concept is known as Inheritance.

The child class must be capable of handling the same requests and returning results of the same type as the parent class.

In the illustration, the parent class delivers Coffee (which could be any kind of coffee). It is acceptable for the child class to deliver Cappuccino, as it is a specific type of Coffee, but delivering Water would be unacceptable.

If the child class fails to meet these requirements, it means the child class has been fundamentally altered and violates this principle.

This ensures that subclasses remain consistent and that the use of inheritance is safe.

Interface Segregation Principle

A class should not be forced to implement interfaces it does not use. Smaller, more specific interfaces are preferable to large, general ones.

Instead of having a general Exercises interface with methods like spinAround(), rotateArms() and wiggleAntennas(), you should create smaller interfaces such as RobotsThatCanSpinAround (with sprinAround()), RobotsThatCanRotateArms (with rotateArms() and RobotsThatCanWiggleAntennas (with wiggleAntennas()), allowing classes to implement only the relevant methods.

This prevents classes from being forced to implement unnecessary methods, making the code more modular and cohesive.

Dependency Inversion Principle

High-level modules should not depend on low-level modules; both should depend on abstractions (interfaces or abstract classes).

High-level Module(or Class): Class that executes an action with a tool.

Low-level Module (or Class): The tool that is needed to execute the action

Abstraction: Represents an interface that connects the two Classes.

In the left image (marked with a red cross), the robot says, “I cut pizza with my pizza cutter arm,” which indicates that the robot (class) is tightly coupled to a specific tool (dependency). This is a violation of the DIP because the robot depends on a low-level module (pizza cutter arm) for its functionality, reducing flexibility and reusability.

public class Robot
{
    private PizzaCutterArm _pizzaCutterArm = new PizzaCutterArm();

    public void CutPizza()
    {
        _pizzaCutterArm.Cut();
    }
}

Here, the Robot class is tightly coupled to the PizzaCutterArm class. It can’t use any other tool because it depends on a specific instance of PizzaCutterArm.

In contrast, the right image (marked with a green check) shows the robot saying, “I cut pizza with any tool given to me.” This is a better design because the robot is not tied to a specific tool; it can use any tool that fits the task. This demonstrates the correct use of DIP, where the robot (high-level module) depends on an abstraction (the ability to cut pizza), not a specific tool (low-level module).

public interface ITool
{
    void Cut();
}

public class Robot
{
    private readonly ITool _tool;

    // Dependency is injected via constructor
    public Robot(ITool tool)
    {
        _tool = tool;
    }

    public void CutPizza()
    {
        _tool.Cut();
    }
}

public class PizzaCutterArm : ITool
{
    public void Cut()
    {
        Console.WriteLine("Cutting pizza with pizza cutter arm.");
    }
}

public class LaserCutter : ITool
{
    public void Cut()
    {
        Console.WriteLine("Cutting pizza with laser cutter.");
    }
}

// Usage
ITool pizzaCutter = new PizzaCutterArm();
Robot robot = new Robot(pizzaCutter);
robot.CutPizza();

// Changing tool easily
ITool laserCutter = new LaserCutter();
Robot robotWithLaser = new Robot(laserCutter);
robotWithLaser.CutPizza();

This promotes flexibility by making it easier to swap implementations, and it makes the system more extensible because you reduce tight coupling between components.


Summary of the Benefits of SOLID Principles

  • Ease of Maintenance: Cleaner, more organised code that makes it easier to identify and fix issues.
  • Extensibility: Software becomes easier to extend with new features without modifying existing code.
  • Reduced Coupling: Classes become less dependent on each other, making it easier to modify and test individual components.
  • Improved Testability: Code that follows SOLID principles is easier to unit test due to its low coupling and high modularity.

By applying these concepts, we can improve code quality, enhance team collaboration, and minimise the risk of introducing errors when adding or modifying functionalities in the system.

Leave a comment

Spam-free subscription, we guarantee. This is just a friendly ping when new content is out.

← Back

Thank you for your response. ✨

Warning
Warning
Warning.