The basic idea behind the Dependency Inversion Principle is that we should create the higher level modules with its complex logic in such a way to be reusable and unaffected by any change from the lower level modules in our application. To achieve this kind of behavior in our apps, we introduce abstraction which decouples higher from lower level modules.

Having this idea in mind the Dependency Inversion Principle states that

  • High-level modules should not depend on low-level modules, both should depend on abstractions.
  • Abstractions should not depend on details. Details should depend on abstractions.

We are going to make all of this easier to understand with an example and additional explanations.

To download the source code for this project, check out Dependency Inversion Principle Project Source Code.

To read about other SOLID principles, check out our SOLID Principles page.

This article is divided into the following sections:

What are the High-Level and Low-Level Modules

The high-level modules describe those operations in our application that has more abstract nature and contain more complex logic. These modules orchestrate low-level modules in our application.

The low-level modules contain more specific individual components focusing on details and smaller parts of the application. These modules are used inside the high-level modules in our app.

What we need to understand when talking about DIP and these modules is that both, the high-level and low-level modules, depend on abstractions. We can find different opinions about if the DIP inverts dependency between high and low-level modules or not. Some agree with the first opinion and others prefer the second. But the common ground is that the DIP  creates a decoupled structure between high and low-level modules by introducing abstraction between them.

Example Which Violates DIP

Let’s start by creating two enumerations and one model class:

To continue, we are going to create one low-level class which keeps (in a simplified way) track of our employees:

Furthermore, we are going to create a higher-level class to perform some kind of statistical analysis on our employees:

With this kind of structure in our EmployeeManager class, we can’t make use of the _employess list in the EmployeeStatistics class, so the obvious solution would be to expose that private list:

Now, we can complete the Count method logic:

Even though this will work just fine, this is not what we consider a good code and it violates the DIP.

How is that?

Well, first of all, our EmployeeStatistics class has a strong relation (coupled) to the EmployeeManager class and we can’t send any other object in the EmployeeStatistics constructor except the EmployeeManager object. The second problem is that we are using public property from the low-level class inside the high-level class. By doing so, our low-level class can’t change its way of keeping track of employees. If we want to change its behavior to use a dictionary instead of a list, we need to change the EmployeeStatistics class behavior for sure. And that’s something we want to avoid if possible.

Making Our Code Better by implementing Dependency Inversion Principle

What we want is to decouple our two classes so the both of them depend on abstraction.

So, the first thing we need to do is to create the IEmployeeSearchable interface:

Then, let’s modify the EmployeeManager class:

Finally, we can modify the EmployeeStatistics class:

This looks much better now and it’s implemented by DIP rules. Now, our EmployeeStatistics class is not dependent on the lower-level class and the EmployeeManager class can change its behavior about storing employees as well.

Finally, we can check the result by modifying Program.cs class:

finished example - Dependency Inversion Principle

Benefits of Implementing the Dependency Inversion Principle

Reducing the number of dependencies among modules is an important part of the process of creating an application. This is something that we get if we implement DIP correctly. Our classes are not tightly coupled with the lower-tier objects and we can easily reuse the logic from the high-tier modules.

So, the main reason why DIP is so important is the modularity and reusability of the application modules.

It is also important to mention that changing already implemented modules is risky. By depending on abstraction and not on a concrete implementation, we can reduce that risk by not having to change high-level modules in our project.

Finally, DIP when applied correctly gives us the flexibility and stability at the level of the entire architecture of our application. Our application will be able to evolve more securely and become stable and robust.

Conclusion

So to sum up, the Dependency Inversion Principle is the last part of the SOLID principles which introduce an abstraction between high and low-level components inside our project to remove dependencies between them.

If someone asks: „Should I put an effort to implement the DIP into my code?“, our answer would be: „Yes you should“.  Loosely coupled code and reusable components should be our goal and responsibility when developing software applications.

If you have enjoyed reading this article and if you would like to receive the notifications about the freshly published .NET Core content we encourage you to subscribe to our blog.