What is Dependency Inversion Principle?
Dependency Inversion Principle corresponds to D among SOLI’D’ Principles. Its principle starts with this statement.
High-level modules should not depend on low-level modules. Both should depend on abstractions.
Well-organized code always has a hierarchy. There is a high-level modules and low-level modules. But sometimes rookie developers misunderstand this concept, and they bring directly low-level modules to high-level modules.
Can you see what’s wrong with this code?
import java.util.Arrays;
import java.util.List;// High Level Module
class ProductCatalog {
public void listAllProducts() {
SQLProductRepository sqlProductRepository = new SQLProductRepository();
List<String> allProductNames = sqlProductRepository.getAllProductNames();
// Display product names
}
}// Low Level Module
class SQLProductRepository {
public List<String> getAllProductNames() {
return Arrays.asList("soap", "toothpaste", "shampoo");
}
}
What do you think? Do you see a problem? ProductCatalog class is high-level module but it depends on its submodule SQLProductRepository.
This is a mistake often made by novice developers.
Let me repeat again. Dependency Inversion Principle says
“High-level modules should not depend on low-level modules. Both should depend on abstractions.”
You are now seeing the violation of Dependency Inversion Principle because High Level Module ProductCatalog depends on its submodule SQLProductRepository.
Now you understand the problem. Then how do you fix it? Before you fix it, let’s see what abstraction means in software design.
What is Abstraction?
(1) Code without abstraction
class Benz {
public void drive() {
}
}
class CarUtil {
public static void drive(Benz benz) {
benz.drive();
}
}
As you can see the code above, CarUtil class’s static drive method is dependent on Benz. You should provide Benz instance in order for CarUtil’s drive() method to function. In software design, it is called ‘tight-coupling’. This also means, when you change drive() method inside Benz class, CarUtil is directly affected. This is prone to make bugs.
Tight Coupling is the most undesirable feature in Software
(2) Code with Abstraction
interface Car {
public void drive();
}
class Benz implements Car {
@Override
public void drive() {
}
}
class Tesla implements Car {
@Override
public void drive() {
}
}
class CarUtil {
public static void drive(Car car) {
car.drive();
}
}
This code looks perfect. CarUtil’s static drive method doesn’t depend on Benz, but it depends on Car interface. Now, it can take any argument which implements Car Interface. This is called abstraction. It is also called ‘loose-coupling’.
Now, let’s go back to the previous code example.
Refactoring Previous Code with Abstraction
“Abstractions should not depend on details. Details should depend on abstractions.”
import java.util.Arrays;
import java.util.List;
// High Level Module
class ProductCatalog {
public void listAllProducts() {
// High Level Module depends on Abstraction
ProductRepository productRepository = new SQLProductRepository();
List<String> allProductNames = productRepository.getAllProductNames();
// Display product names
}
}interface ProductRepository {
List<String> getAllProductNames();
}
// Low Level Module
class SQLProductRepository implements ProductRepository {
public List<String> getAllProductNames() {
return Arrays.asList("soap", "toothpaste", "shampoo");
}
}
Now, ProductCatalog depends on ProductRepository instead of SQLProductRepository.
Why doing this again?
First, you don’t know what database you are going to use. It may not be specifically SQL. Second, ProductCatalog‘s listAllProducts() does not depend on a specific object. This means, when you change code in SQLProductRepository, ProductCatalog is not directly affected. You just have achieved loose-coupling.
What you have achieved now
Before refactoring
Before refactoring, ProductCatalog was dependent on SQLProductRepository.
After Refactoring
After Refactoring, ProductCatalog depends on ProductRepository and SQLProductRepository is also dependent on ProductRepository .
Let me remind you of Dependency Inversion Principle again.
“High-level modules should not depend on low-level modules. Both should depend on abstractions.”
I think you now understand the sentence entirely!
One strep forward, Dependency Injection
import java.util.Arrays;
import java.util.List;
class ProductCatalog {
private ProductRepository productRepository;
public ProductCatalog(ProductRepository productRepository) {
this.productRepository = productRepository;
}
public void listAllProducts() {
List<String> allProductNames = productRepository.getAllProductNames();
// Display product names
}
}
interface ProductRepository {
List<String> getAllProductNames();
}
class SQLProductRepository implements ProductRepository {
public List<String> getAllProductNames() {
return Arrays.asList("soap", "toothpaste", "shampoo");
}
}
class EcommerceApplication {
public static void main(String[] args) {
ProductRepository productRepository = new SQLProductRepository();
ProductCatalog productCatalog = new ProductCatalog(productRepository);
productCatalog.listAllProducts();
}
}
You’re now injecting ProductRepository to ProductCatalog. This is a common practice and even recommended way to build objects. You will understand its usefulness when you learn Mock and TDD concepts. But if I explain it now, I can’t keep the promise of “The Easiest Tutorial of The World”.
Conclusion
“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.”
Source: Medium - KD Knowledge Diet
The Tech Platform
Comments