Why use SOLID Principles in your design?
Using these principles leads to more maintainable, extensible code, that can be refactored more easily and facilitates Agile software development. By using SOLID principles the programmer can avoid code smells that may not have been picked up.
So what does the SOLID acronym stands for:
- S - Single Responsibility Principle
- O - Open-closed Principle
- L - Liskov Substitution Principle
- I - Interface Segregation Principle
- D - Dependency Inversion Principle
In today's article, we will go through these principles and discuss them in a bit more detail. Hopeful together we can get a better understanding of the SOLID principles and using them can make us better developers.
SOLID Principles key aspects
Single-Responsibility Principle (SRP)
The Single-Responsibility principle states that each class should only have one job. Practically this means that a class should also only have one reason to change.
That means that each class should only contain properties and methods related to that class. For example, if you have a class named DataAccess then all the items in that class should only be related to DataAccess. In other words, your DataAccess class should only contain items related to data access and not logging or reading files for example.
Names of classes should also be simple and clear, so that it is easy to understand at a glance what the class is used for.
Open Closed Principle
The open-closed principle suggests that entities such as classes, modules, and functions should only be open for extension, not modification.
What does that mean?
In simple terms, it means that you should be able to add more functionality to code, without changing existing code. The great thing about writing code this way means that changing code in one of your classes would not also need you to change depending classes.
You could do this with inheritance, however, there is a risk here that you might introduce tight coupling in your code if the child classes rely on the implementation details from the parent class.
Instead, polymorphism could be used. This uses interfaces instead of parent classes, therefore removing the details of the implementation that a parent class would provide. For details on the difference between using inheritance or polymorphism read our article on object-orientated programming.
This means that classes and interfaces are loosely coupled. Implementation of interfaces are independent and don't rely on each other.
When creating your interfaces you would only include critical parts of your class and not any optional ones. This allows the interface to be as flexible as it can be and wouldn't limit its implementation.
Liskov Substitution Principle
Liskov Substitution Principle is the third of the SOLID principles. It defines that objects of parent classes are replaceable by objects of subclasses without breaking the application. This means that the subclass must behave in all the same ways a parent class would.
For example if you override a method in a subclass, this needs to accept the same parameter values a parent class would. You can be less restrictive in your parameter specifications, e.g. use a long instead of an int, therefore ensuring that the parent class and the subclass would both work.
You cannot, however, be more restrictive in subclass parameters as that would mean the parameters might not work on the parent class.
The issue with this is that there is no way the compiler will be able to enforce a specific behaviour of your code. A compiler would only check if the structure is correct. Your own checks and tests will need to be put in place.
Interface Segregation Principle
Robert C. Martin defined the Interface Segregation Principle as follows:
"Clients should not be forced to depend upon interfaces that they do not use."
Robert C. Martin
The goal here is for you to reduce or attempt to eliminate the number of changes that would be needed in your code if one is needed.
Very similar to the Single Responsibility Principle mentioned earlier, this principle suggests that you should not add any methods to existing interfaces if it does not match the responsibility of the interface. Doing this results in a "bloated interface", where the interface has several responsibilities.
If you add methods to interfaces that aren't needed by all the classes that implement those interfaces then you have to write extra code to override those methods. It would be better if a new interface is created for the new method.
Dependency Inversion Principle
The Dependency Inversion Principle suggests that High-level modules should not depend on low-level modules. Using abstractions help us as developers to implement this principle.
If you've already implemented other parts of the SOLID principle, you may have already implemented the Dependency Inversion Principle. Namely, the Open/Closed Principle and Liskov Substitution Principle.
By implementing these two principles your classes should already be using interfaces for abstraction between high level and low level classes. They are also open for extension but closed for modification. The only final thing to do is potentially use dependency injection to avoid compile-time dependency.
Doing all of this it should enable you to make changes to a high level and low-level classes without affecting other classes.
Conclusion on the SOLID principles
The SOLID design principles help you as a developer to write code that is robust, maintainable and easily extensible. By following these principles your application will be much more maintainable. What are your thoughts on using the SOLID principles?
Did you find this article valuable?
Support Michael Asaad by becoming a sponsor. Any amount is appreciated!