Table of Contents
Today I would like to talk about S.O.L.I.D., the first five principles of object-oriented programming that we at Apiumhub (like many others), find essential for building working software. In case you didn’t know it, in computer programming, the SOLID principles acronym was introduced by Michael Feathers for five principles that were defined by Robert C. Martin in the early 2000s.
As you know, to get a working software, we should have a low coupling, high cohesion and strong encapsulation, which is something that the SOLID principles help us obtain. The idea is that, by applying those principles together, you are able to write better quality code that is robust. The system created becomes easy to maintain, to reuse and to extend over time. Basically, SOLID principles help software developers to achieve scalability and avoid that your code breaks every time you face a change.
Ok, so let’s start with the basics, S.O.L.I.D. stands for:
S – Single-responsibility principle
O – Open-closed principle
L – Liskov substitution principle
I – Interface segregation principle
D – Dependency Inversion Principle
Let’s look at each principle individually to understand why S.O.L.I.D can help developers to build quality software.
The SOLID Principles
1.Single-responsibility principle
“There should be never more than one reason for a class to change.”
As you can see, this principle states that an object / class should only have one responsibility and that it should be completely encapsulated by the class. Here, when we talk about a responsibility, we mean a reason to change. This principle will lead to a stronger cohesion in the class and looser coupling between dependency classes, a better readability and a code with a lower complexity.
It is much more difficult to understand and edit a class when it has various responsibilities. So if we have more than one reason to change, the functionality will be split into two classes and each will handle its own responsibility.
We care about separating the functionalities because each responsibility is an access of change. When a class has more than a single responsibility, those responsibilities become coupled and this coupling can lead to a fragile code base that is difficult to refactor when your requirements emerge.
If you’re interested in knowing a bit more about the SRP principle, here’s a post you will enjoy reading.
2. Open-closed principle
“Software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification.”
Here, the idea is that an entity allows its behaviour to be extended but never by modifying its source code. Any class (or whatever you write) should be written in such a way that it can be used as is. It can be extended if need be, but it can never be modified. You can consider this when you are writing your classes. Use the class in any way you need, but modifying its behaviour comes by adding new code, never by modifying the old. The same principle can be applied for modules, packages, libraries.
By applying the open-closed principle you will get a loose coupling, you will improve readability and finally, you will be reducing the risk of breaking existing functionality.
3. Liskov substitution principle
“subtypes must be substitutable for their base types”
As it’s name says it, Likov’s Substitution Principle was defined by Barbara Liskov. The idea here is that objects should be replaceable by instances of their subtypes, and that without affecting the functioning of your system from a client’s point of view. Basically, instead of using the actual implementation, you should always be able to use a base class and get the result you were waiting for. Often when we want to represent an object, we model our classes based on its properties and instead of that, we should actually be putting more our focus on the behaviours.
This principle basically confirms that our abstractions are correct and helps us get a code that is easily reusable and class hierarchies that are very easily understood.
What many say is that Liskov’s Substitution Principle has a very strong relation with the previous principle, the open-closed principle. Robert C. Martin even says that “a violation of LSP is a latent violation of OCP”.
4. Interface segregation principle
“Classes that implement interfaces, should not be forced to implement methods they do not use.”
Here, it’s about how to write interfaces. So what is stated? Basically, once an interface is becoming too large / fat, we absolutely need to split it into small interfaces that are more specific. And interface will be defined by the client that will use it, which means that client of the interface will only know about the methods that are related to them.
Actually, if you add methods that shouldn’t be there, the classes implementing the interface will have to implement those methods as well. That is why; client shouldn’t be forced to depend on interfaces that they don’t use. ISP is intended to keep a system decoupled and thus easier to refactor, change, and deploy.
5. Dependency Inversion Principle
“High level modules should not depend on low level modules rather both should depend on abstraction. Abstraction should not depend on details; rather detail should depend on abstraction.”
Last of the SOLID principles but not least, this principle is primarily concerned with reducing dependencies amongst the code modules. Basically, the Dependency Inversion Principle will be of a great help when it comes to understanding how to correctly tie your system together.
If your implementation detail will depend on the higher-level abstractions, it will help you to get a system that is coupled correctly. Also, it will influence the encapsulation and cohesion of that system.
Conclusion
When developing any software, there are two concepts that are very important: cohesion (when different parts of a system will work together to get better results than if each part would be working individually) & coupling (can be seen as a degree of dependence of a class, method or any other software entity).
Coupling is usually present in a lot of code and as I mentioned earlier, the optimal situation would be to have a low coupling and a high cohesion. With this brief introduction to the 5 SOLID principles, you must have understood that they help us when it comes to that.
There are so many principles in software engineering and I would recommend that before writing a code, you should do your research, read and try to understand the principles. Although it may seem like a lot, SOLID becomes a part of you and your code by using it continuously and adapting its guidelines.
If you are working on a software project and you need help with software architecture, let us know! We would be happy to know more about it!
If you enjoyed this, you might like…
- SOLID Principles Cheatsheet [Printable PDF]
- The adapter pattern in object-oriented programming
- Functional debt vs. technical debt in software development
- Code encapsulation; keep calm and hide your code
- How to solve Javascript Callback Hell?
2 Comments
VD
Nicely written but, in Liskov substitution principle section, I think there is a mistake:
“instead of using the actual implementation, you should always be able to use a base class and…”
Correct me if I’m wrong here but instead of “base class” there should be “child class” because the child class can “do” everything that a parent class can plus more. Right?
Michael
“We care about separating the functionalities because each responsibility is an access of change.”
I think you mean _axis_ of change.
The Open-Closed Principle is not about source code. It’s about behavior as you state for the LSP. You can add new behavior to your entities, but you should never change existing behavior. If it were about source code, you’d never be allowed to fix bugs, although it has certainly happened many times over that a piece of software comes to depend on the behavior of a bug thereby preventing the maintainers from modifying the behavior of the original code and instead forcing them to introduce a new corrected version with a different name while deprecating the old one.
For languages like Java and C#, the LSP is best implemented using interfaces instead of class inheritance, but that’s a language-specific detail that doesn’t necessarily conflict with your description of the LSP. It is important for programmers of those languages to understand this, however, since you can’t really implement the LSP effectively without using interfaces in those languages.
The Interface Segregation Principle is not about “size” which has no objective benchmark but rather again about behavior. A good example of a violation of the Interface Segregation Principle is in Java Collections. Mutability is built into nearly the entire hierarchy. In order to facilitate their goal of easy parallelism with the Streams API, they introduced “unmodifiable” implementations of their mutable classes which throw exceptions on method calls that would otherwise result in mutation. This is a terrible design because it now violates both the Open-Closed Principle and the LSP since I can pass an unmodifiable collection as an argument to a method that attempts to mutate it and throws an exception instead of being different in a way that is indistinguishable for the user. If instead, they had introduced a new Collections package that has an immutable hierarchy with segregated mutable extensions (in other words, treated mutability as functionality on top of collections that don’t inherently expose the ability to modify them), they could have had a much better design that adhered to S.O.L.I.D. principles at the cost of forcing developers to learn a new set of collection classes to take advantage of their new API.