It is time to see the fifth and last principle of SOLID: the Dependency Inversion Principle, also known as DIP. If you missed the other principles, you can learn more about them by following these links:
- Single Responsibility Principle (SRP)
- Open Closed Principle (OCP)
- Liskov Substitution Principle (LSP)
- Interface Segregation Principle (ISP)
When developing software you will have a lot of different modules having each their role and responsibility. You will have to connect these modules between them in order to create the desired functionalities for your system. They will have dependencies between them and it will increase the coupling between your components. So it is important to reduce the risk involved in these dependencies. This is where the DIP rules come in play:
- High-level modules should not depend on low-level modules. Both should depend on abstractions.
- Abstractions should not depend upon details. Details should depend upon abstractions.
When a high-level module (business service for instance) depends on low-level module (data validator, a repository, …) it is difficult to reuse this module because it is highly coupled.
I have created an example that violates the DIP and I will show a way to solve the issue.
public class User { public string UserName { get; set; } // some more properties } public class UserRepository { public User GetUser(string userName) { // retrieve user in Database return new User(); } } public class NotificationService { public void Send(string message, User user) { // populate a message with the user information // send the message } } public class MessageSender { // Send a message to a specific user public void SendMessage(string message, string userName) { var userRepository = new UserRepository(); var user = userRepository.GetUser(userName); var notificationService = new NotificationService(); notificationService.Send(message, user); } }
In this piece of code my high-level module is the MessageSender, it allows me to send a given message to a given user (found by its username). For example this is a class that can be used from a user interface with a form. You can see that it has dependencies on a repository and a service, a modification of one of them can have impacts on my class. And what if I want to retrieve my user through a web service instead of the database? Or if I want to be able to send message to my friend via Twitter, Facebook or another social network? I will have to add complexity in this module even if its responsibility does not change from a “business” point of view.
My code is highly coupled and violates the Dependency Inversion Principle, I have to change it and I will by using abstraction (interfaces in my case).
public interface IUserRepository { User GetUser(string userName); } public class UserRepository : IUserRepository { public User GetUser(string userName) { // retrieve user in Database return new User(); } } public interface INotificationService { void Send(string mesage, User user); } public class NotificationService : INotificationService { public void Send(string message, User user) { // populate a message with the user information // send the message } } public class MessageSender { public INotificationService NotificationService { get; set; } public IUserRepository UserRepository { get; set; } // Send a message to a specific user public void SendMessage(string message, string userName) { var user = UserRepository.GetUser(userName); NotificationService.Send(message, user); } }
In this new version I only use contracts/abstractions for my high-level component and this way I reduced the coupling with the implementations of the low-level modules. I decided to expose the dependencies via properties to access them and set them in order to clean the “SendMessage” method. When using this class I can now specify if I want to use an EmailNotificationService, a TwitterNotificationService, another social network related service or even a test double if I am in a test context.
Imagine creating automated tests with the first implementation! I would have to create an actual database for my UserRepository and a STMP service for my email based NotificationService. All of this only to test this little piece of logic, with abstraction and dependency inversion it is much easier now to test my components. A few months ago I introduced a DIP pattern known as Dependency Injection, you can learn more here.
This is the end of my presentation of the Dependency Inversion Principle and the end of the SOLID principles as well. I hope you like it and do not hesitate to leave a comment if you want to improve the concepts I have introduced in these blog posts.
See you next time!