Dependency Inversion Principle
Quick question, what’s the difference between the following commonly heard software engineering principles?
- “Dependency Inversion Principle”
- “Program to an interface, not an implementation”
- “Depend on abstractions, not on concretions”
The answer is none. They are all saying the same thing in different ways.
Dependency Inversion Principle
Let’s talk about the first one, first haha.
This principle says something like
“High-level modules should not depend on low-level modules. Both should depend on abstractions.”
An interface is an abstraction, so this is essentially saying that both high and low level modules should just depend on interfaces. Why? The same reason why you you use interfaces any other time, to shield yourself from changes in concrete implementations.
Say, you are ComponentA
and you need to use ComponentB
. If you use a concrete instance of ComponentB
, you are now tightly coupled to it. Anytime ComponentB
changes, you (ComponentA
) may have to change as well. This thing, where when you need to make a single change and multiple places are affected, is, bad.
And the problem isn’t the labor of having to change multiple places, it’s when you forget a place, and as a result introduce a very nasty subtle bug. And if you want to be a fast programmer, you want to prevent subtle bugs from getting in in the first place.
So instead, you depend “on the interface” of ComponentB
. In other words, extract an interface from ComponentB
and depend on that. Now, ComponentB
can change all it wants, and you’re not affected by it.
The second part of Dependency Inversion Principle says
“Abstractions should not depend on details. Details should depend on abstractions.”
This is just saying that your “details” (the actual lines of code) should try not to mention any concrete types. Instead, define your variables to be of an interface type (and use factories or dependency injection to provide the concrete implementations).
So, in summary, the Dependency Inversion Principle is saying 1) both your high and low level modules should depend on interfaces (and not each other), and 2) your implementations should use interfaces in the actual code (e.g. variables of an interface type).
Program to an Interface, not an Implementation
So how is all that different from “Program to an Interface, not an Implementation”? It’s not. It’s just another way of saying the same thing.
When you “program to an interface”, your “details” (the actual lines of code) are using interfaces. This is the same as saying “details should depend on abstractions”. And if you follow “program to an interface” true to heart, then you are also making sure your components depend on each other via interfaces, thus there is no difference here.
Depend on abstractions, not on concretions
We’ve already established that “abstractions” and interfaces are the same thing for our purpose, so this again is saying the same thing.
Conclusion
All three of these common sayings are really saying the same exact thing: Use interfaces between components that need to interact, to shield them from changes in each other. Such that when one needs to be changed, the other doesn’t (so long as the interface doesn’t change).
And again, the reason you can depend on an interface is because by their nature, they change way less frequently than concrete implementations.
Bonus: Modularity
By following these principles, and also by following the liskov substitution principle, you are making your system very modular. Because now, going back to our initial example of ComponentA
depending on ComponentB
, you can easily swap out ComponentB
for ComponentC
or ComponentD
or ComponentE
or whatever, so long as they implement the same interface, ComponentA
don’t care.
So you see, all of these principles kinda tie in together to help you create modular, loosely coupled systems that are easy to maintain and extend.
That’s it! Have an awesome day!