# Dependency Inversion Principle
- [Dependency Inversion Principle](#dependency-inversion-principle)
- [Intro](#intro)
- [eCommerce Example (bad design)](#ecommerce-example-bad-design)
- [eCommerce Example (better design)](#ecommerce-example-better-design)
- [Dependency Injection](#dependency-injection)
## 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)
```{uml}
@startuml
skinparam DefaultFontName Source Code Pro
skinparam DefaultFontSize 15
skinparam RankSep 50
package "eCommerce" {
node "High Level Components" {
component ProductCatalog
component PaymentProcessor
component CustomerProfile
}
interface Communication
note right of Communication
Communication is both
low and hight level module.
end note
node "Low Level Components" {
component SQLProductRepository
component GooglePayGateway
component WireTransfer
component EmailSender
component VoiceDialer
}
ProductCatalog ..> SQLProductRepository: depends on
PaymentProcessor ..> GooglePayGateway: depends on
PaymentProcessor ..> WireTransfer: depends on
CustomerProfile ..> Communication: depends on
Communication ..> EmailSender: depends on
Communication ..> VoiceDialer: depends on
}
@enduml
```
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):
```{uml}
@startuml
skinparam DefaultFontName Source Code Pro
skinparam DefaultFontSize 15
class ProductCatalog {
+listAllProducts(): void
}
class SQLProductRepository {
+getAllProductNames(): List
}
ProductCatalog -up-> SQLProductRepository: uses
@enduml
```
## 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.
```{uml}
@startuml
skinparam DefaultFontName Source Code Pro
skinparam DefaultFontSize 15
skinparam RankSep 50
package "eCommerce" {
node "High Level Components" {
component ProductCatalog
}
node "Abstractions" {
component ProductFactory
}
node "Low Level Components" {
component SQLProductRepository
}
ProductCatalog ..> ProductFactory: depends on
ProductFactory ..> SQLProductRepository: depends on
}
@enduml
```
Here's a class diagram for the `ProductCatalog` high-level component:
```{uml}
@startuml
skinparam DefaultFontName Source Code Pro
skinparam DefaultFontSize 15
interface ProductRepository {
+getAllProductNames(): List
}
class SQLProductRepository {
+getAllProductNames(): List
}
class ProductFactory {
{static} create(): ProductRepository <>
}
class ProductCatalog {
+listAllProducts(): void
}
ProductCatalog -r-> ProductFactory: uses
ProductCatalog --> ProductRepository: depends on
SQLProductRepository .l.|> ProductRepository: implements
ProductFactory --> ProductRepository: uses
ProductFactory --> SQLProductRepository: uses
ProductRepository --> SQLProductRepository: depends on
@enduml
```
Now, our reference type is `ProductRepository` (an interface), making it loosely coupled with `SQLProductRepository`.
```java
public class ProductCatalog {
public void listAllProducts() {
ProductRepository productRepository = ProductFactory.create();
List 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:
```{uml}
@startuml
skinparam DefaultFontName Source Code Pro
skinparam DefaultFontSize 15
interface ProductRepository {
+getAllProductNames(): List
}
class ECommerceApplication {
+main(): void
}
class SQLProductRepository {
+getAllProductNames(): List
}
class ProductFactory {
{static} create(): ProductRepository <>
}
class ProductCatalog {
+ProductCatalog(ProductRepository: repository): void
+listAllProducts(): void
}
ECommerceApplication -l-> ProductCatalog: uses
ECommerceApplication -d-> ProductFactory: uses
ProductCatalog --> ProductRepository: depends on
SQLProductRepository .r.|> ProductRepository: implements
ProductFactory --> ProductRepository: uses
ProductFactory --> SQLProductRepository: uses
ProductRepository --> SQLProductRepository: depends on
@enduml
```
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.