If you've ever written Object-Oriented (OO) code, you've probably heard of S.O.L.I.D principles. You may not have fully grasped the concepts, but let's dive into them today (I'll discuss their shortcomings in the next episode).
Single Responsibility
Each class should have only one responsibility!
Let's say you created a class named Open
. It doesn't really matter what it opens; if you ask it to open a DB connection, it will do so. If you ask it to open a file in 'w' mode, it will do that too. There's no real difference. But this is not a good solution because the more responsibilities a class has, the higher the chance it will contain bugs. Instead, it is recommended to create separate classes like OpenDBConnection
, OpenFile
, and OpenFolder
.
Open/Closed Principle
Classes should be open for extension but closed for modification.
Take the OpenFolder
class as an example. You could add functionality to it for opening files, but without changing or removing any existing functionality. For example, you can create a separate method like open_file(filename: str, mode: str)
and use the OpenFile
class for simplicity. This way, you're not adding extra logic but instead using single-purpose classes as mentioned earlier.
Liskov Substitution Principle
If class B is a subclass of class A, then an object of class B should be able to replace an object of class A without affecting the functionality.
Don’t rush to judge. To simplify, this means the child class should be able to do everything the parent class can do. For instance, if the son asks, "Hey, Dad, I can't make the coffee, can you make it for me?" the father shouldn’t respond with "Drink some water, I can't make coffee." The son should still be able to make the coffee. This is what we mean by the child class being able to perform the same tasks as the parent class. I think there’s no need for an overly complicated example, this should suffice. :)
Interface Segregation Principle
Clients should not be forced to depend on methods they do not use.
For example, the OpenFile
class doesn’t need to connect to a database. Forcing it to do so would be unnecessary and counterproductive. Each class should only handle the tasks relevant to its specific responsibility. To make it clearer, even static methods should follow this principle and not become a hassle. 😉
Dependency Inversion Principle
High-level modules or classes should not depend on low-level modules or classes. Instead, both should depend on abstractions.
- High-level class: the performer of the task.
- Low-level class: the tool performing the task.
- Abstraction: the connector between the two.
For example, consider a TV remote. The remote should not depend on the TV's internal mechanisms. If the TV's internal mechanism breaks, you wouldn't call a technician for the remote; you’d call for the TV. Instead, the remote should be an abstraction that can be fixed separately. If the remote’s battery is dead, you wouldn’t open the TV to fix it—you’d replace the remote battery. If the remote breaks, you don’t need to fix the TV, you just fix the remote. In this case, abstraction serves as the connection between the two.
The above concepts have been briefly explained to make them easy to understand. The goal was to provide a general idea of these principles. In future posts, we will dive deeper into each one, discussing their pros and cons in detail.