Dependency Inversion Principle#
Intro#
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.
eCommerce Example (bad design)#
Is Communication
a high-level module, or a low-level module? Tricky question. It is both.
From the CustomerProfile
module’s perspective, then Communication
is a low-level module, but from the EmailSender
and VoiceDialer
modules, it is high-level.
A module being high- or low-level is not an absolute fact, but a relative one. It depends on the context of the module relative to the surrounding modules.
Remember:
High-level modules should not depend on low-level modules. Both should depend on abstractions.
Our “depends on” associations goes directly against the principle above. Our hight-level modules should depend on abstractions instead.
ProductCatalog
(a high-level module) is directly depending on SQLProductRepository
(a low level-module), violating the Dependency Inversion Principle.
Here’s a class diagram of our bad design (considering only ProductCatalog
for now):
eCommerce Example (better design)#
We should not instantiate SQLProductRepository
directly. Instead, we should create a ProductRepository
interface, and create a product repository factory abstraction to be used by `ProductCatalog.
Here’s a class diagram for the ProductCatalog
high-level component:
Now, our reference type is ProductRepository
(an interface), making it loosely coupled with SQLProductRepository
.
public class ProductCatalog {
public void listAllProducts() {
ProductRepository productRepository = ProductFactory.create();
List<String> products = productRepository.getAllProductNames();
for (String product : products) {
System.out.println(product);
}
}
}
What about this?
Abstractions should not depend on details. Details should depend on abstractions.
The low-level modules are the modules that deal with the details. Note that now ProductRepository
depends on SQLProductRepository
, and not the other way around. Great, we are following the guideline above as well.
Dependency Injection#
Notice even though we delegated the responsibility of creating a product repository to a factory, ProductCatalog
is still responsible for bootstrapping this instantiation. Ideally, ProductCatalog
should not worry about where and when this instantiation should occur.
We could provide the instantiated product repository to the ProductCatalog
class even without it explicitly asking for it.
Here’s the class diagram of this new design:
We introduced a new class ECommerceApplication
that provides an instance of ProductRepository
to ProductCatalog
through its constructor. That relieves ProductCatalog
from worrying about where and when to instantiate a ProductRepository
.
Dependency Injection not only avoids tight coupling, and goes further as it completely dissociates a class from going out of its way to instantiate its dependencies.