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.
Let's understand theHigh-level modulesandLow-level modulesthrough an example:
In an e-commerce app like Flipkart on a high level it can be categorized as ProductCatalog, PaymentProcessor, and CustomerProfile (these are some of the main business functions)
These business functions interdepend on other modules shown in the above image.
Note: the modules on top are closer to a business function calledHighlevel modules.
The modules at the bottom are close to the implementation details calledLowlevel modules.
Low-level modules are SQLProductRepository, GooglePayService, WireTransfer, EmailSender, and VoiceDialer.
If we consider CustomerProfile (High-Level module) and Communication modules alone then, Communication is a Low-level module but if we consider Communication, EmailSender, and VoiceDialer alone then, Communication becomes a High-Level module, and EmailSender and VoiceDialer are Low-Level modules.
The point here the concept ofHigh and Low level moduleis not absolute butrelative.
According to the image above ProductCatalog depends on SQLProductRepository i.e. a high-level module depends on a low-level module, but this directlyconflicts with the DIP's 1st definition.
Let's take ProductCatalog → SQLProductRepository relationship and analyze it further.
import java.util.List; /* * High-Level module */ public class ProductCatalog { public void listAllProducts(){ SQLProductRepository sqlProductRepository = new SQLProductRepository(); ListallProductsNames = sqlProductRepository.getAllProductNames(); //Display all products names } }
/* * Low-level module */ import java.util.Arrays; import java.util.List; public class SQLProductRepository { public ListgetAllProductNames(){ return Arrays.asList("soap","toothpaste"); } }
As ProductCatalog directly depends on SQLProductRepository this is clearly a violation of DIP definition 1 (as per the definitionboth High and Low-level modules should depend on abstraction)
Let's fix this as per definition 1:
creating interface ProductRepository
import java.util.List; public interface ProductRepository { public ListgetAllProductNames(); }
Implementing this interface in SQLProductRepository
/* * Low-level module */ import java.util.Arrays; import java.util.List; public class SQLProductRepository implements ProductRepository{ @Override public ListgetAllProductNames(){ return Arrays.asList("soap","toothpaste"); } }
Finally for High level module ProductCatalog weshould notdirectly instantiate SQLProductRepository in it. We will use a ProductFactory class for the same
public class ProductFactory { public static ProductRepository create(){ return new SQLProductRepository(); } }
We will use ProductFactory to instantiate the SQLProductRepository
/* * High-Level module */ import java.util.List; public class ProductCatalog { public void listAllProducts(){ ProductRepository productRepository = ProductFactory.create(); ListallProductsNames = productRepository.getAllProductNames(); //Display all products names } }
Note our reference object is ProductRepository So, we don't have any tight coupling with SQLProductRepository
After the modification the new dependency will look something like this
The above changes are as per the DIP definition 1.
The above code change also follows the 2nd definition of DIP as well i.e. Abstraction should not depend on the details, details should depend on the abstraction.
As we can see in the image above SQLProductRepository depends on the ProductRepository not the other way around.This is the reason why this principle is called the Dependency Inversion Principle
Even though they are related, they are not the same and can not be used interchangeably
Understanding Dependency Injection:
In ProductCatalog we make use of the Factory method ProductFactory.create() to get an instance of SQLProductRepository object.
Though it delegates the instance creation process to the factory class ProductFactory, the initialization process is still with the ProductCatalog class.
Ideally, we don't want ProductCatelog class to worry about how and when to trigger the instantiation.
What if we provide the instantiated ProductRepository class to ProductCatalog even without it asking?
So, the Main class ECommerceMainApplication makes use of the factory method ProductFactory.create() to create the instance of ProductRepository and this instance is passed as an argument in the constructor of ProductRepositroy class.
public class ECommerceMainApplication { public static void main(String agrs[]) { ProductRepository productRepository = ProductFactory.create(); ProductCatalog productCatalog = new ProductCatalog(productRepository); productCatalog.listAllProducts(); } }
After updating the ProductCatalog class accordingly
import java.util.List; public class ProductCatalog { private ProductRepository productRepository; public ProductCatalog(ProductRepository productRepository) { this.productRepository = productRepository; } public void listAllProducts(){ ListallProductsNames = productRepository.getAllProductNames(); //Display all products names allProductsNames.forEach(product-> System.out.println(product)); } }
Now the ProductCatalog is free to use the SQLProductRepository object whenever and wherever it wants. It no longer has not worry about creating the SQLProductRepository object on its own.
In other wordswe are injecting the dependencyinto the ProductCatalog instead of ProductCatalog worrying about instantiating the dependency.
This is the concept ofdependency injection
Even though it is not part of DIP(Dependency Inversion Principle), it is closely related
Let us understand this with the same above code
The class ProductCatalog had a constructor that took in ProductRepository object.
The class that calls the ProductCatalog will provide or inject the object of ProductRepository in this case it is ECommerceMainApplication.
Note, even though the injection happens outside of the ProductCatalog class, theinjection still happens during the main flow of the program. i.e. the injection is happening in the main thread of the program execution.
What if we want all the injections to happen in a separate thread or a separate context altogether So that the main control flow is completely isolated from the injection?
This can be achieved using frameworks likeSpring(in Java).
Spring will run its own context differentfrom the main flow of the program
Spring will take care of injecting the required dependencies of a class. So if you want to instantiate the object of a class, instead of doing it yourself directly in the code, you ask Spring to give you the object of the class.
The Spring framework looks at all the dependencies required for the instantiation of the object, then goes ahead and injects all the dependencies, instantiates the object, and gives it back to the main control flow.
Thus the control over dependency injection is completely delegated to the Spring framework and does not happen in the mail control flow.
This concept is called Inversion of Control (IOC)and the Spring is called Inversion of Control Container or simply anIOC Container
The above is the detailed content of Dependency Inversion Principle. For more information, please follow other related articles on the PHP Chinese website!