NailYourInterview
SOLID Principles

Dependency Inversion Principle Explained with Java Example

Learn the Dependency Inversion Principle with a simple Java example. Discover how using abstractions makes your code flexible and testable.

Dependency Inversion Principle (DIP)

Video thumbnail

The D in the SOLID principles stands for the Dependency Inversion Principle. By the end of this chapter, you'll be able to confidently answer:

  1. What is the Dependency Inversion Principle?

  2. Why do we need it? (What problems arise if we don’t follow it?)

  3. How do we apply it in real-world code?

What is Dependency Inversion Principle (DIP)

The Dependency Inversion Principle (DIP) states that high-level modules should not depend on low-level modules. Instead, both should depend on abstractions (like interfaces or abstract classes). This helps decouple the components of the system.

If you're new to terms like high-level modules and low-level modules, don’t worry! Let’s break it down with an example.

Without Dependency Inversion (Bad Approach)

Let's first look at what happens if we don't follow DIP.

Example where Dependency Inversion is violated
Without Dependency Inversion

In this diagram, we see that the service layer is the high-level module (which handles complex logic), while the repository layer is the low-level module (which performs tasks like saving, updating, or retrieving data from a database).

Currently, the service layer is tightly coupled with the repository layer. Let’s see this with some code:

public class UserService {
    // tightly coupled with SQL repository
    SQLRepository repository = new SQLRepository();
 
    public void get(String id) {
        // for MongoRepository, it should be repository.find(id);
        repository.get(id);
    }
}

In this case, the UserService class is tightly coupled to the SQLRepository. If we decide to switch from SQL to MongoDB, we would need to modify the UserService class, making the code less flexible and harder to maintain.

With Dependency Inversion (Good Approach)

To follow the Dependency Inversion Principle, we introduce an abstraction (an interface), IRepository, which is then implemented by the repositories (SQLRepository, MongoRepository, etc.). This way, UserService depends on the abstraction, not on a specific repository implementation.

Example where Dependency Inversion is achieved
With Dependency Inversion

Here’s how we can refactor the code:

public interface IRepository {
    void get(String id);
}

Now, the UserService is no longer dependent on a specific repository class. It can work with any class that implements the IRepository interface, whether it's the SQLRepository, MongoRepository, or FakeRepository.

UserService.java
public class UserService {
    // decoupled the repository from the service layer
    IRepository repository;
 
    public UserService(IRepository repository) {
        this.repository = repository;
    }
 
    public void get(String id) {
        // no need to change to repository.find() for MongoRepository
        repository.get(id);
    }
 
    public void setRepository(IRepository repository){
        this.repository = repository;
    }
}

Here’s the Main class that shows how we can use different repositories without modifying UserService:

Main.java
public class Main {
    public static void main(String[] args) {
        String id = "xyz";
        UserService userService = new UserService(new SQLRepository());
 
        // fetched from MySQL DB
        userService.get(id);
 
        // point the userService to MongoDB (no need to change service code)
        userService.setRepository(new MongoRepository());
 
        // fetched from MongoDB
        userService.get(id);
    }
}

Benefits of Dependency Inversion

  • Decoupling: By depending on abstractions (interfaces or abstract classes) instead of concrete implementations, we reduce the dependencies between components. This makes the code easier to modify and extend.

  • Flexibility: It's much easier to switch from one implementation to another (for example, switching from SQL to MongoDB) without affecting other parts of the system.

  • Maintainability: Since changes in low-level modules (like repositories) don’t affect high-level modules (like the service layer), the code becomes easier to maintain and scale.

On this page