Skip to main content

Dependency Inversion Principle (DIP)

The Dependency Inversion Principle (DIP) is one of the SOLID principles in object-oriented design. It states that high-level modules should not depend on low-level modules. Both should depend on abstractions, meaning that we should use interfaces or abstract classes to decouple our systems.

Understanding the Dependency Inversion Principle​

Dependency Inversion Principle (DIP): High-level modules, which provide complex logic, should not depend on low-level modules, which provide utility features. Instead, both should depend on abstractions (interfaces or abstract classes). This allows for flexibility and ease of maintenance.

Without Dependency Inversion (Bad Approach)​

Let's consider an example where the Dependency Inversion Principle is violated:

Without Dependency Inversion

Without Dependency Inversion

ProductSQLRepository.java
public class ProductSQLRepository {
public void addProduct() {
System.out.println("Product is added to SQL DB");
}

public void getProduct() {
System.out.println("Product is fetched from SQL DB");
}
}
ProductService.java
public class ProductService {
ProductSQLRepository repository;

public ProductService(ProductSQLRepository repository) {
this.repository = repository;
}

public void addProduct() {
repository.addProduct();
}

public void getProduct() {
repository.getProduct();
}
}

In this scenario, ProductService is tightly coupled with ProductSQLRepository. If we decide to switch from a SQL database to another database like MongoDB, we would need to modify the ProductService class, making our code less flexible and harder to maintain.

Note

Higher Level Module (Service Class) should not depend on Lower Level Module (Repository Class); both should depend on abstractions like interfaces.

With Dependency Inversion (Good Approach)​

To follow the Dependency Inversion Principle, we introduce an interface, IRepository, which ProductRepository implements. This way, ProductService depends on an abstraction rather than a concrete implementation.

With Dependency Inversion

With Dependency Inversion

Defining the Interface​

IRepository.java
public interface IRepository {
void addProduct();
void getProduct();
}

Implementing the Interface​

ProductFakeRepository.java
public class ProductFakeRepository implements IRepository {
@Override
public void addProduct() {
System.out.println("Product is added to Fake DB");
}

@Override
public void getProduct() {
System.out.println("Product is fetched from Fake DB");
}
}
ProductMongoRepository.java
public class ProductMongoRepository implements IRepository {
@Override
public void addProduct() {
System.out.println("Product is added to Mongo DB");
}

@Override
public void getProduct() {
System.out.println("Product is fetched from Mongo DB");
}
}
ProductSQLRepository.java
public class ProductSQLRepository implements IRepository {
@Override
public void addProduct() {
System.out.println("Product is added to SQL DB");
}

@Override
public void getProduct() {
System.out.println("Product is fetched from SQL DB");
}
}

Using the Interface in ProductService​

ProductService.java
public class ProductService {
IRepository repository;

public ProductService(IRepository repository) {
this.repository = repository;
}

public void addProduct() {
repository.addProduct();
}

public void getProduct() {
repository.getProduct();
}
}

Main Class Example​

Main.java
public class Main {
public static void main(String[] args) {
ProductService sqlProductService = new ProductService(new ProductSQLRepository());
ProductService mongoProductService = new ProductService(new ProductMongoRepository());

sqlProductService.addProduct();
sqlProductService.getProduct();

mongoProductService.addProduct();
mongoProductService.getProduct();
}
}

Benefits of Dependency Inversion​

  • Decoupling: By depending on abstractions rather than concrete implementations, the code becomes more modular and decoupled.

  • Flexibility: It becomes easier to switch out one implementation for another without affecting the rest of the codebase.

  • Maintainability: The code is easier to maintain and extend, as changes in low-level modules do not ripple up to high-level modules.