S.O.L.I.D Principles
S = Single Responsibility
O = Open/Closed
L = Liskov Substitution
I = Interface Segregation
D = Dependency Inversion
Introduction
To make it easy to follow, I'll use the term "Class" but note that it can apply to a Function, Method, or Module in this article.
Single Responsibility
single responsibility
A class should have only one responsibility.
You can't be everything at once. You can't be a chef, gardener, painter, and driver all at the same time.
If a class has many responsibilities, it increases the possibility of bugs because making changes to one of its responsibilities may affect the others without you knowing.
The goal of this principle is to separate behaviors. If bugs appear, they affect only one responsibility.
Open/Closed
open/closed
A class should be closed for modifications but open for extensions.

Changing a class's behavior will affect all systems that use that class. If you want a class to have more functions, it's better to assign an existing function to it, not create a new function inside it, meaning, close it for modifications and extend its behavior.
Illustration: Don't use the same hand to cut and paint, use one hand to cut and another to paint.
Liskov Substitution
This was the hardest one to understand in my opinion.

Basically, if we have a class and create a subclass using inheritance, the object or instance of that subclass should be able to replace the superclass without breaking the code.
I understood nothing. Wait!
A simple explanation: imagine you have a parent class called bird:
class Bird {
void Fly(){
System.out.println("I can fly");
}
void Peck(){
System.out.println("I can peck");
}
}
class Woodpecker extends Bird{
@Override
void Fly() {
super.Fly();
}
@Override
void Peck() {
super.Peck();
}
}
In this example, we can use the methods from the Bird
class easily in the Woodpecker class, but imagine creating a new Penguin
class that cannot fly.
class Penguin extends Bird{
@Override
void Peck() {
super.Peck();
}
// Penguin cannot fly therefore doesn't implement Fly()
}
If you were using the Fly()
method from your parent Bird class in the code and were to replace Bird
with Penguin
, an exception would occur because Penguin
doesn't implement Fly()
Interface Segregation
Interface Segregation
Clients should not be forced to depend on methods they don't use

When a Class is forced to perform actions that are not useful, it's a waste and can produce unexpected bugs if the Class doesn't have the capability to perform these actions.
A Class should perform only the actions necessary to fulfill its role. Any other action should be completely removed or moved elsewhere if it can be used by another Class in the future.
Goal
This principle aims to divide a set of actions into smaller sets so that a Class performs ONLY the set of actions it requires.
Dependency Inversion
Dependency Inversion
High-level modules should not depend on low-level modules. Both should depend on abstractions.
Abstractions should not depend on details. Details should depend on abstractions.

First, let's define the terms used here more simply:
-
High-level modules: Classes that perform an action with a tool.
-
Low-level modules: The tool needed to perform the action.
-
Abstraction: Represents an interface that connects the two classes.
-
Details: How the tools work.
This principle states that a Class should not be fused with the tool it uses to perform an action. Instead, it should be fused to the interface that will allow the tool to connect to the Class.
It also states that neither the Class nor the interface should know how the tool works. However, the tool needs to meet the interface's specification.
Goal
This principle aims to reduce a high-level Class's dependency on the low-level Class by introducing an interface.