Dependency Inversion Principle
Last updated
Last updated
Definition: High-level modules should not depend on Low-level Modules, but both should depend on abstractions.
The normal flow of module dependencies: High-level Modules -> Medium Level Modules -> Low-level Modules.
What we want to achieve: High-level Modules -> Medium Level Abstractions <- Medium Level Modules -> Low-level Abstractions <- Low-level Modules.
DI Principle helps in achieving a very decoupled code that helps in extension and testing.
To achieve true dependency inversion, every dependency should be explicit (no new keywords inside the module).
To achieve explicit dependencies we can use dependency injection either in Constructors, Method Parameters, property injection, or even using IoC Containers (Inversion of Control).
Extract Dependencies into interfaces
Inject Implementations of interfaces
Apply SRP
The problem here is actually obvious, the UserService is coupled with this exact NotificationService, testing the UserService alone is impossible, and of course, it became much harder to run tests as NotificationService may rely on things not available in the current time (a special mail server or something).
One solution is to make the NotificationService an explicit dependency, but don't forget that polymorphism will not work, as NotificationService is a concrete class not an abstraction.
Now as the dependency with the NotificationService is decoupled and relies on an abstraction, the NotificationService implementation can be swapped in runtime with other concrete classes that implement the same abstraction.
Also now writing Unit tests for UserService doesn't rely on the NotificationService anymore as a mock can be written to replace the real NotificationService during the tests.