Dependency Inversion
Dependency Inversion is a no-brainer for fixing tight coupling issues, but why is it named "Dependency Inversion" and not just "the better way to do abstraction" TM Pending?
The twist is that instead of high-level classes depending directly on low-level ones, they depend on interfaces, which the low-level classes then implement and thus the “inversion” isn’t about how you pass around the interface—it’s about flipping the usual dependency flow, making everything more flexible and easier to manage.
The key here is the dependency is being flipped from the injected interface/implementer, not the class that performing the composition. This is a summary for you that definitely wasn't created by chat-gippity or related AI summarisation tools.
What is Dependency Inversion?
Dependency Inversion is a principle in software design that addresses the direction of dependency within an application. It is part of the SOLID principles and aims to reduce coupling between high-level modules and low-level modules.
Key Concept:
-
Typical Dependency Flow: In a traditional design, if
Class A
usesClass B
, andClass B
usesClass C
, thenClass A
has a direct dependency onClass B
, andClass B
has a direct dependency onClass C
. This creates a direct dependency graph fromClass A
toClass C
. -
Inverted Dependency Flow: By applying the Dependency Inversion Principle,
Class A
interacts withClass B
through an abstraction (interface or abstract class) thatClass B
implements.Class B
will depend on an interface controlled byClass A
, inverting the traditional compile-time dependency. At runtime,Class A
can still call methods onClass B
through the abstraction, but the compile-time dependencies are reversed.
Why is it Called Dependency Inversion?
The term "Dependency Inversion" reflects the change in dependency direction:
- Before Inversion: Dependencies flow from high-level modules to low-level modules directly.
- After Inversion: Dependencies flow from high-level modules to abstractions, and low-level modules implement these abstractions.
This inversion means that instead of high-level modules depending on low-level modules, both depend on abstractions. This inversion enhances modularity, flexibility, and testability.
Example of Dependency Inversion
Without Dependency Inversion:
public class LowLevelService
{
public void DoWork() { /* Implementation */ }
}
public class HighLevelController
{
private LowLevelService _service;
public HighLevelController()
{
_service = new LowLevelService();
}
public void PerformAction()
{
_service.DoWork();
}
}
In this example, HighLevelController directly depends on LowLevelService. This direct dependency makes HighLevelController tightly coupled to LowLevelService.
With Dependency Inversion:
public interface IService
{
void DoWork();
}
public class LowLevelService : IService
{
public void DoWork() { /* Implementation */ }
}
public class HighLevelController
{
private readonly IService _service;
public HighLevelController(IService service)
{
_service = service;
}
public void PerformAction()
{
_service.DoWork();
}
}
In this refactored example:
- IService is an abstraction that LowLevelService implements.
- HighLevelController depends on the IService interface rather than a specific implementation.
- This allows for greater flexibility and easier testing. For instance, you can easily substitute LowLevelService with a mock or another implementation of IService.
Just don't be that person who generates interfaces anytime, everywhere "just because SOLID".
© Jason Song.