A Simple Guide to the Dependency Inversion Principle
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.
This is a well-known principle called the “Dependency Inversion Principle.”
Today, I’m going to give you the simplest explanation of this principle:
Dependency Inversion is the strategy of relying on interfaces or abstract functions and classes rather than concrete functions and classes.
In simple terms, when one class needs an object from another class, it requires a dependency. Instead of directly injecting the dependency into the class, we should use a level of abstraction between them.
Let's understand this with an example:
Imagine we are building a car, which has many parts such as the steering, brakes, gears, engine, etc.
Now, we are building a new heavy-duty engine and want to test this prototype. We find that whenever we update the car's engine, we also need to fix the steering and gears. This should not happen.
This means that the car engine (a low-level module) should not depend on the main features of the car like the steering and gears (high-level modules). Instead, they should depend on abstractions whenever possible.
Also, the abstraction for connecting the engine (low-level module) and steering (high-level module) should not depend on the implementation details of the engine (low-level module).
Implementation details should depend on abstractions. If the abstraction between the steering and the car engine depends on the implementation details of the car engine, any future changes to the engine will affect the abstraction. It will then also affect other modules that implement this abstraction.
The best approach is implementation details, which should depend on the abstraction. This way both modules will only have to implement the abstraction, rather than the implementation details of others directly.
The benefit of this way is we can easily change the engine on the go, and our car will not break, because we will make sure our new engine should also implements the abstraction.
This principle solve the problem of:
- Tight Coupling that leads to cascade changes
- Lack of Flexibility for future implementation
- Violation of the Open/Closed Principle
- Testability Issues
- Increase complexity
To sum up, the Dependency Inversion Principle tells us that every dependency in the design should target an interface or an abstract class. Furthermore, a dependency should not target a concrete class.
This way, we can easily change the dependency. For example, if I want to test my car with different types of engines like a Mercedes engine, Bugatti engine, Audi engine, etc., I just need to make sure each engine can implement the abstraction, which can be an interface or an abstract class.
The main point of DIP is to help our modules be truly independent. It makes it easier to develop and deploy an application independently.
Subscribe to my newsletter
Read articles from Muhammad Abubakar directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Muhammad Abubakar
Muhammad Abubakar
A Senior Software Engineer, Who Loves learning Software Engineering Internals, having core mastery of Frontend, but a keen interest in backend, infra, and devOps.