DEV Community

Cover image for A solid explanation to SOLID Principle
Aman Kr Pandey
Aman Kr Pandey

Posted on

A solid explanation to SOLID Principle

A piece of software rarely fails because the "the algorithm was wrong". It mostly fails slowly because features become hard to add, bugs reappear and even a small change feels risky. SOLID principles give a five very useful practical ways to write code so that it remains understandable and and can adapt to new changes for a significantly longer period of time.

What is SOLID?

It's definitely not one among the three states of matter. SOLID here stands as acronym of 5 design principle that help us write highly maintainable code. The principles are as follows:

  • Single responsibility Principle
  • Open-Close Principle
  • Liskov's Substitution Principle
  • Interface Segregation Principle
  • Dependency Injection Principle Together, they encourage loose coupling, clear responsibilities, and stable abstractions, which are essential for long-lived code.

Single Responsibility Principle

Single Responsibility Principle (SRP) states that a class should have one and only one reason to change. Let's say for example we are implementing a class to manage library. An example of bad code that does not follow SRP would be:

This class has four reasons to change:
1.Change in validation logic for user data
2.Change in email templates or email api/client
3.Change in log format
4.Change in DB schema will change the query inside saveToDatabase method

Apart from these it UserService class will be hard to reuse as no one will want to trigger email, and save to database operation when validating user data. This kind of classes are also a testing nightmare as you will have to mock logger, email and db clients.

Better idea would be to have separate class for each one of them.
Below is the example of good code that follows SRP, with each responsibility handled with separate classes.

In the above code each class has only one reason to change and each class is independent of each other, so anyone can use login to validate user or sending email separately, with this email and logging logic can be used in different parts of the code.

So our first mantra to achieve good design is Ek class Ek Kaam.

Open/Close Principle

Open/Close Principle(OCP) states that classes should be open for extension but closed for modification. This principle encourages us to use abstraction instead of concrete class so when we need new behaviors we can add new class instead of modifying the existing behavior of the tested code. This reduces risk while adding new features as you are not modifying the already tested code.

Let's understand OCP via shapes to find the area. The below code calculates the area of the shapes like rectangle and circle.

Let's say if you need to add a new shape, for example a square, if will change the implementation of calculateArea in AreaCalculator class and this is a violation of OCP.

Now a good code that does not violate OCP for the same feature would be as follows.

Here we have created an abstract class Shape that has a abstract method area. This method is overridden in different shape classes like Rectangle. The AreaCalculator class only knows that is is receiving a Shape and it has to call area method on it without worrying about the implementation of area of each shape. In this design it will be easier to introduce new shape like Square with changing implementation of any other class.

So, in order to keep this principle in mind remember the mantra - Extend karo, edit nahi.

Liskov's Substitution Principle

Liskov's substitution principle (LSP) suggestes that a child class can substitute a parent class without changing the correctness or expected behavior of the code. The core idea is, if S is a child class of T, then any piece of code that works with T as object should always work with S as object with out breaking the code or changing the behavior of the code.

Let's understand this with this simple example. Suppose you are making a bird game with different birds that can fly or walk and the following code controls these behaviors

Bird contract promises fly() works for any bird, but Ostrich breaks this promise by throwing an exception. Clients cannot safely substitute Ostrich for Bird and Ostrich's fly() changes the behavior of the code.

Now, we know that all birds can walk but all birds can not fly. So why not have multiple abstraction for each behavior and then a Bird class implementing only the common behavior, like something explained below.

In this code makeWalk() method expects a super-type Bird but sub-classes like Ostrich and Sparrow can substitute it without changing the behavior walk() in respective classes.

In order to remember this easily keep in mind Agar bache ka behavior parents ke jaise hai to wo party me ja sakta hai.

Interface Segregation Principle

The Interface Segregation Principle (ISP), part of the SOLID design principles, states that clients should not be forced to implement interfaces/method they do not use. Instead of large, general-purpose interfaces, we should create smaller, specific ones according to client. This approach reduces tight coupling and makes systems easier to maintain.

In the above code, a single IWorker interface requires all clients to provide eat(), even robots that only work. This violates ISP, creating dummy methods and tight coupling.

A goof practice would be to have separate interfaces Workable and Eatable, and client can implement any combination of the interface as per their requirement. So we refactor the above code to follow ISP.

This reduces coupling and supports extensibility, like adding Rechargeable for robots.

To easily remember this keep in min Divide and Rule: Divide the interface and rule the SOLID principle!

Dependency Injection Principle

This is one of my favourite and most widely used principle, the Dependency Injection Principle (DIP). DIP states that high-level components should not depend on low-level components, both should depend on abstractions. Let's understand with an example.
Let's you are writing a code for making Tea. You can have different types of Tea: Masala Tea, Ginger Tea, Gold tea etc. A simple code to do this would be as follows.

class MasalaTeaMaker {
    public String makeTea() {
        return "Masala Tea ready!";
    }
}

class TeaShop {
    private MasalaTeaMaker maker = new MasalaTeaMaker();  // Hardcoded dependency!

    public String orderTea(String type) {
        if ("masala".equals(type)) {
            return maker.makeTea();
        }
        return "Only Masala available";  // Rigid!
    }
}
Enter fullscreen mode Exit fullscreen mode

The problem here is tight coupling of MasalaTeaMaker with TeaShop, and adding new type of TeaMaker would be risky as it would need us to update TeaShop class, for example if we add GingerTeaMaker also in the code

class MasalaTeaMaker {
    public String makeTea() {
        return "Masala Tea ready!";
    }
}

class GingerTeaMaker {
    public String makeTea() {
        return "Ginger Tea ready!";
    }
}

class TeaShop {
    private MasalaTeaMaker masalaTeaMaker = new MasalaTeaMaker();
    private GingerTeaMaker gingerTeaMaker = new GingerTeaMaker();

    public String orderTea(String type) {
        if ("masala".equals(type)) {
            return masalaTeaMaker.makeTea();
        }else if("ginger".equals(type)){
          return gingerTeaMaker.makeTea();
        }
        return "Un-supported tea type"; 
    }
}
Enter fullscreen mode Exit fullscreen mode

Now, TeaShop is high level which should depend on TeaMaker via abstraction and not through concrete class. So we will no refactor this code and create an interface TeaMaker.

TeaMaker interface lets GingerTeaMaker, GoldTeaMaker implement it. TeaShop depends on abstraction via injection. In the above code injection is done through a constructor, but injections can also be done via setter methods if needed.

As a memory map you can remember Abstraction pe depend karo class pe nahi.

With this we have completed SOLID Principles, for any queries feel free to use comments.

Thanks for reading, stay tuned!

Top comments (0)