The S.O.L.I.D design principles
Simply explanation of SOLID principles
You’ve probably heard of the S.O.L.I.D principles if you’re familiar with Object-Oriented Programming. These S.O.L.I.D principles serve as guidelines for creating software that is easy to scale and maintain. Robert C. Martin, a software engineer, popularized them.
My main goal in writing this article is to help you understand these principles better by using illustrations and emphasizing the goal for each one.
When designing software, S.O.L.I.D stands for five object-oriented principles that should be followed.
- S → Single Responsibility Principle
- O → Open-Closed Principle
- L → Liskov Substitution Principle
- I → Interface Segregation Principle
- D → Dependency Inversion Principle
1. Single Responsibility Principle
A class should have a single responsibility.
When a class has a lot of responsibilities, it’s more likely to have bugs because changing one of them could affect the others without you realizing it.
Let’s take an example:
public class Student{
public void printDetails();
pubic void calculatePercentage();
public void addStudent();
}
The single responsibility principle is violated in the above code snippet. To achieve the principle’s goal, we should create a separate class that only performs a single function.
public class Student{
public void addStudent();
}public class PrintStudentDetails{
public void printDetails();
}public class Percentage{
public void calculatePercentage();
}
Now, we have achieved the goal of the single responsibility principle by separating the functionality into three separate classes.
2. Open-Closed Principle
Classes should be open for extension but closed for modification.
Changing the current behaviour of a Class will have an impact on all systems that use it. If you want the Class to perform additional functions, the best approach is to add to the existing functions rather than changing them.
public class AnimalInfo{
public double animalNumber(Animal a){
if (a instanceof Cat){
return a.getNumber();
if (a instanceof Dog){
return a.getNumber();
}
}
We simply add another if statement that violates the open-closed principle if we want to add another subclass named Rat. Overriding the animalNumber() method is the only way to add the subclass and achieve the principle’s goal, as shown below.
public class AnimalInfo{
public double animalNumber(){
//functionality
}
}
public class Cat extends AnimalInfo{
public double animalNumber(){
return this.getValue();
}
}
public class Rat extends AnimalInfo{
public double animalNumber(){
return this.getValue();
}
}
Easily, we can add more animals by making another subclass extending from the AnimalInfo class. the approach would not affect the existing application.
3. Liskov Substitution Principle
If S is a subclass of T, then T objects in a program can be replaced with S objects without affecting any of the program’s desirable properties.
public class Rectangle{
private double length;
private double width;
public void setLength(double l){
length = l;
}
public void setWidth(double w){
width = w;
}
}public class Area extends Rectangle{
public void setLength(double l){
super.setLength(l);
super.setWidth(w);
}
public void setWidth(double w){
super.setLength(l);
super.setWidth(w);
}
}
Because the Area class has additional constraints, such as length and width that must be the same, the above classes violated the Liskov substitution principle. As a result, the Area class (derived class) cannot be used in place of the Rectangle class (base class).
As a result, replacing the Rectangle class with the Area class may result in unexpected behaviour.
4. Interface Segregation Principle
Clients should not be forced to use methods that they are unfamiliar with.
In here, we have created an interface named Conversion having three methods intToDouble(), intToChar(), and charToString().
public interface Conversion{
public void intToDouble();
public void intToChar();
public void charToString();
}
If we want to use only a method intToChar(), the principle allows us to split the interface into three separate ones as below:
public interface ConvertIntToDouble{
public void intToDouble();
}
public interface ConvertIntToChar{
public void intToChar();
}
public interface ConvertCharToString{
public void charToString();
}
Now we can use only the method that is required. Suppose, we want to convert the integer to character and character to string then, we will use only the methods intToChar and charToString.
5. Dependency Inversion Principle
- High-level modules should not depend on low-level modules. Both should depend on the abstraction.
- Abstractions should not depend on details. Details should depend on abstractions.
public class House{
//functionality
}
we create a constructor of the class and add the instances of the door and window as below:
public class House{
public final door;
public final window;
public House(){
window = new window(); //instance of window class
door = new door(); //instance of door class
}
}
To make the code loosely coupled, we decouple the House from the door by using the Door interface and this keyword.
public interface Door{
//functionality
}public class House{
private final Door door;
private final Window window;
public House(Door door, Window window){
this.window = window;
this.door = door;
}
}
In the above, we have used this principle to add door dependency in the House class. Therefore, we have decoupled the classes.
Summary
These five principles have been discussed thus far. They’re supposed to make it simple for you to adjust, extend, and test your code.
Thank you so much for taking the time to read this. I hope you now have a better understanding of S.O.L.I.D principles and that you enjoyed reading it as much as I enjoyed writing it.