Where did the SOLID principles come from?
How many times have you written a simple app and then needed to add new features?
After repeating this process several times, the application became complex and untouchable. Once you make a modification in a particular class, you find that a new problem has appeared in another place, then you realize that there is no solution other than making a refactor for the application, do not worry, these problems have faced many programmers, but the solution It came when Robert C. Martin - known as Uncle Bob - issued his research paper entitled "Design Principles and Design Patterns paper" in which he talked about the most important principles of Clean Code, the most important of which is SOLID Principles.
Define SOLID principles
Not as the name SOLID might imply, something that makes code static quite the opposite, these ideas are about how code can be flexible, scalable, maintainable, and adaptive to project changes.
SOLID is an acronym of the five principles that make up them, so that the first letter of each principle was taken, as we will see:
1-Single responsibility principle.
3-Liskov substitution principle.
4-Interface segregation principle.
5-Dependency inversion principle.
⦁ Single Responsibility Principle
The Single Responsibility Principle is the first design principle of SOLID, represented by the letter "S".
This principle states that each class has only one responsibility and one reason for the change.
This means that every class, or similar structure, in your code should have only one job to do. Everything in that class should be related to a single purpose. It does not mean that your classes should only contain one method or property. There may be many members as long as they relate to a single responsibility.
The Single Responsibility Principle gives us a good way of identifying classes at the design phase of an application and it makes you think of all the ways a class can change. A good separation of responsibilities is done only when we have the full picture of how the application should work. Let us check this with an example.
All looks good until you would want to reuse the “checkAccess” code at some other place OR you want to make changes to the way “checkAccess” is being done. In all 2 cases you would end up changing the same class and in the first case you would have to use “UserSettingService” to check for access as well.
One way to correct this is to decompose the “UserSettingService” into “UserSettingService” and “SecurityService”. And move the “checkAccess” code into “SecurityService”.
⦁ The principle is open-closed
This Principle is represented by the letter "O" of the Five SOLID Principles.
The Open/closed Principle says "A software module/class is open for extension and closed for modification".
Here "Open for extension" means, we need to design our module/class in such a way that the new functionality can be added only when new requirements are generated. "Closed for modification" means we have already developed a class and it has gone through unit testing. We should then not alter it until we find bugs. As it says, a class should be open for extensions, we can use inheritance to do this. Okay, let's dive into an example.
Suppose we have a Rectangle class with the properties Height and Width.
Our app needs the ability to calculate the total area of a collection of Rectangles. Since we already learned the Single Responsibility Principle (SRP), we don't need to put the total area calculation code inside the rectangle. So here I created another class for area calculation.
Hey, we did it. We made our app without violating SRP. No issues for now. But can we extend our app so that it could calculate the area of not only Rectangles but also the area of Circles as well? Now we have an issue with the area calculation issue because the way to do circle area calculation is different. Hmm. Not a big deal. We can change the “TotalArea” method a bit so that it can accept an array of objects as an argument. We check the object type in the loop and do area calculation based on the object type.
Wow. We are done with the change. Here we successfully introduced Circle into our app. We can add a Triangle and calculate it's the area by adding one more "if" block in the “TotalArea” method of “AreaCalculator”. But every time we introduce a new shape we need to alter the “TotalArea” method. So the “AreaCalculator” class is not closed for modification. How can we make our design to avoid this situation? Generally, we can do this by referring to abstractions for dependencies, such as interfaces or abstract classes, rather than using concrete classes. Such interfaces can be fixed once developed so the classes that depend upon them can rely upon unchanging abstractions. Functionality can be added by creating new classes that implement the interfaces. So let's refract our code using an interface.
Inheriting from Shape, the Rectangle and Circle classes now look like this:
Every shape contains its area with its own way of calculation functionality and our AreaCalculator class will become simpler than before.
Now our code is following SRP and OCP both. Whenever you introduce a new shape by deriving from the "Shape" abstract class, you need not change the "AreaCalculator" class. Awesome. Isn't it?