SOLID is an acronym for the 5 most important design principles of OOP (by Robert C. Martin). These principles establish practices that provide software maintainability and extensibility as the project expands.
SOLID stands for:
- S = Single-Responsiblity Principle
- O = Open-closed Principle
- L = Liskov Substitution Principle
- I = Interface Segregation Principle
- D = Dependency Inversion Principle
Single Responsibility Principle
“A class should have one and only one reason to change, meaning that a class should have only one job.”
Lets take as example an application which uses an ORM to persist objects in database. Customer entity class has attributes like name, surname etc …
Add a save() method in Customer class.
To add a custom save() method to all entities of the application, code would have to change in all of these places when is needed.
Create one global Repository class to handle the persist calls to Database
Customer class should be refactored as per below…
Hence, the single responsibility of Repository class is to persist entity objects into the Database.
“Objects or entities should be open for extension but closed for modification.”
Lets take as example an application which has a class to execute processes
A class called ProcessManager has a method runProcess(), which runs the process code based on the process name as the parameter.
Each time a new process has to be created, a new if-else statement has to be added with a new code block to be written in this class.
Make ProcessManager class runProcess() method to take as parameter an interface called IProcess. IProcess interface in our example is implemented by a concrete class, CRUDProcess.
Liskov Substitution Principle
“Let q(x) be a property provable about objects of x of type T. Then q(y) should be provable for objects y of type S where S is a subtype of T.”
Every subclass class should be substitutable for their parent class. That is, objects of a superclass should be replaceable with objects of its subclasses without breaking the application.
A subclass overridden method requires to accept the same input parameters as the method of the superclass. Moreover, you must implement less restrictive validation rules, but you are not allowed to enforce stricter ones in your subclass.
Lets look at an example using ShapeCalculator where we have 2 shapes Square and Triangle. Both have methods for calculating their area.
We have a generic parent class ShapeCalculator which consists of 2 methods for calculating the area of a Square and for a Triangle.
As you can see from above code, the problem is that when the subclasses of type Square and Triangle call both methods, there is an exception thrown for the unimplemented method in the corresponding subclass (SquareCalculator/TriangleCalculator).
This is a violation of the Liskov principle since both child classes are not implementing correctly their parent class behavior.
To fix this violation, we will create a more generic method calculateArea() in the parent ShapeCalculator class.
Interface Segregation Principle
“A client should never be forced to implement an interface that it does not use, or clients shouldn’t be forced to depend on methods they do not us”
That is, for example lets say we have an AnimalFunction Interface which has the below methods…
Cat class has to also implement the fly() method. But cats do not fly!
Modify Interface AnimalFunction and leave only the generic eat() method. In addition, create another Interface called BirdFunction which has the fly() method.
Dependency Inversion Principle
“Entities must depend on abstractions, not on concretions. It states that the high-level module must not depend on the low-level module, but they should depend on abstractions.”
Lets say we have a class which loads and saves entities in the database. We also have a dependency in the BusinessObject class which uses Repository class.
If later we want to change the Repository class we also have to change BusinessObject class. The BusinessObject class should not care about the Repository implementation and/or the database connection implementation.
Create Interface IRepository and in addition Repository class to implement this interface as shown below. Therefore, BusinessObject class depends on IRepository as abstraction and not on the concrete Repository class.
In this article we presented the 5 SOLID principles of Object Oriented design. Using them we provide code readability, extensibility, maintainability. In extend, code modules and specific functions can be isolated and tested, easy to be modified and refactored with the minimum effort.
Below a sample Spring MVC application, that is making use of the above principles …