SOLID Principles of Object-Oriented programming.
- Single Responsibility principle
- Open-closed principle
- Liskov Substitution Principle
- Interface Segregation Principle
- Dependency Inversion Principle
Those are design principles to always keep in mind when coding to make better software. They make software more maintainable, flexible, and testable.
Single Responsibility Principle
This is about the separation of concerns. A class should only have a single responsibility.
Another way to define this is that “a class or module should have one, and only one, reason to be changed”, this is because the class is supposed to have, as mentioned, only one responsibility. This will lead us to have a better understanding of objects’ responsibilities, it will be easier to find bugs and also our code will be way less error-prone when changes have to be done.
We could have the following code:
Above we can see that Employee not only has the responsibility to show its info but also to validate format for some of its attributes. We are adding extra responsibility to the class, then we violate SRP.
Below you can see a code achieving the same goal but following SRP guides:
Now Employee is free of the responsibility to validate the email and Email is the one doing that. We clearly separated responsabilities.
This sets an entity should be “open for extension, but closed for modification”, meaning that an entity’s behavior should not be modified but instead it should be extended.
The typical example for this is the geometric shape and the area calculation, check out the following example:
From above, what if we want to add at some point another geometric figure like a circle? We could say easily we create the new class Circle and then we modify area method from AreaCalculator class
For sure our need to have Circle is meet but can you see how ugly is now area method? We now receive a generic object and we need to check what kind of object it is. Additionally, what if we need to add more and more geometric figures? THat’s s reason to keep in mind the open-closed principle when coding. A better solution could be as follows
Now AreaCalculator is closed for modification, no matter what geometric figure we need to add, it will accept any that is confirming the new protocol GeometricFigure. Also, it is open for extension.
Liskov Substitution Principle
This is about an object being able to be substituted for its subtype without altering any functionality or behavior of the program.
Above we can see that the notion of a square inheriting from the Rectangle is not good since the square’s width must be equal to the square’s height. That requires to add an extra step to validate the equality or any other workaround. This is a violation of Liskov principle since substituting base type (Rectangle) to its subtype (Square) alters the result, so a Square is not exactly a rectangle.
A solution for this could be abstract both Rectangle and Square to have a common parent with all the common methods and attributes.
Interface Segregation Principle
This is about segregating protocols to be more granular, so it is better to have many protocols than just one big protocol with many things.
If we define a big protocol, we potenitally end up having classes that conform to the protocol and depending on methods that they don’t use. Because of that, it is recommended to keep protocols small .. Also, a protocol could implement another protocol if needed.
A typical example of this is
We can see that we have a simpleATM which can’t provide the functionality to deposit money and since it is implementing ATM, it ends up having that method but not using it. There is a clear violation of the Interface segregation principle.
The solution as mentioned is to have a more granular ATM definition and should be something as follows
Dependency Inversion Principle
This principle says that high-level modules should not depend on low-level modules. Both should depend on abstractions. This adds anabstraction and both the high and low-level modules depend on abstraction. Also the abstraction should not depend on details, instead, details should depend on abstraction.
Above we can see a high-level class Notifier class depending on a low-level class NotificationReceiver, calling its method receiveNotification. That way if for any reason we need to change NotificatonReceiver, we also would need to change Notifier.
To invert this dependency we can create a protocol Receiver and have both Notifier and NotificationReceiver depending on it.
Now Notifier expects any entity conforming Receiver protocol, so now it does not depend specifically on NotificationReceiver.
Remember to always keep in mind SOLID principle to have better code!