By Arun Kumar
SOLID principles define the best practices framework that is useful for creating an application or writing code. It helps write efficient, reusable, bug-free code and create an efficient architecture to handle it.
In this article, we will study the SOLID framework and its defined rules that are based on five principles. These 5 principles sum up to form the acronym SOLID. Let's look into each of them.
The single responsibility principle states that a class should have only one responsibility on it. This also implies that it should have only one reason to change. It is quite daunting to understand this. Demonstrating a practical example as below.
Below is an example of a car entity that has few fields.
For displaying data to the end layer of the application, a method is required which will display the data currently to the console.
For displaying the data other than consoles such as an Excel or text document, there is a need for another method/methods. This would break the single responsibility principle. This is because the class is now responsible for basic operations on the Car entity and printing output in the desired format.
For fixing this problem, there is a need to create another class ‘CarPrinter’. This class would handle all the printing-related responsibility for the Car entity. This demonstrates the single responsibility principle.
This principle states that any class should be always open for extension and should be closed for further modification. This implies that any new functionality should be added as an extension for a class and the original class should not be modified. This ensures that no new bugs are introduced to the existing functionality.
Adding a feature maxNitroSpeed for racing cars, which is a derived entity from previous car entity. Rather than modifying the existing car code, a new class called RacingCar is created. This class will extend the base Car class and will have the extra features it needs.
The existing functionality of the Car class is not affected. Thus, no new bugs related to Car would occur due to the new change in feature addition of the RacingCar entity.
This principle states that if class A is a subtype of class B, then A should be able to replace B without any disruptions in the program. To understand the idea of the Liskov substitution, interface Bird is created below. The interface has 2 methods: fly and walk. There is a pigeon entity that implements the bird interface.
After the addition of the pigeon entity, 1 more entity is to be added called penguin which falls under the criteria of bird entity. The problem with this approach is penguins can’t fly. This would lead to an error in the fly method which is wrong. As class Penguin now cannot replace Bird which violates the Liskov substitution principle.
Separate interfaces are needed for FlyingBirds and NonFlying Birds. Pigeons can implement FlyingBird and Penguins can implement NonFlyingBird.
As seen above, the correct allocation of interfaces to respective entities is done. Penguins can replace nonFlyingBird and pigeons can replace FlyingBird. This behavior is in line with the Liskov substitution principle.
This principle states that any larger interface should be split into multiple smaller interfaces. By doing this, the classes implementing them are just concerned with the methods they are interested in. For understanding the segregation of interfaces, an interface FeedAnimals is shown. This interface takes care of feeding animals.
There are 2 methods called feedElephants and feedLions. Considering feeding a lion is a dangerous task, only an experienced person should be allowed to feed a lion. Following the previous statement, 2 interfaces are created out of the FeedAnimals interface as below:
After the segregation of interfaces, the experienced person can feed the lion and the naive person can feed the elephant.
This principle implies that there should be decoupling between modules of an application. High-level modules and low-level modules should depend on abstractions. High-level modules should not depend on low-level modules. Below is the practical use case of the dependency inversion principle. The example is continued from the previous Car entity.
The car entity below is a modified version of the base car entity. An attribute model is added to the entity which defines the model of a car.
Currently models are initialized as BMWModel when Car entity is instantiated. This in turn takes away the ability to use any other car model by tightly coupling the Model with Car. Thus, Model and Car need to be decoupled for the dependency inversion principle to be applied.
After the changes, Car entity can now accept different models and is decoupled from the Model class
The SOLID principles state very fascinating and important rules that bring immense benefits to your code. Here are some to list down a few:
Overall, these principles define the quality of code and design of any application. Every developer should be aware of it and should implement it.