Dependency Inversion Principle

Depend on abstractions, not concretions

Overview

The Dependency Inversion Principle (DIP) states that:

  1. High-level modules should not depend on low-level modules. Both should depend on abstractions.
  2. Abstractions should not depend on details. Details should depend on abstractions.

This principle is the foundation of Dependency Injection and enables loose coupling.

Key Concepts

Depend on abstractions (interfaces), not concrete classes

High-level modules define interfaces

Low-level modules implement interfaces

Enables easy swapping of implementations

Foundation for Dependency Injection

Code Example

java
1// ❌ BAD: High-level depends on low-level (concrete classes)
2class MySQLDatabase {
3    public void save(String data) {
4        System.out.println("Saving to MySQL: " + data);
5    }
6}
7
8class UserService {
9    private MySQLDatabase database;  // Direct dependency on concrete class!
10    
11    public UserService() {
12        this.database = new MySQLDatabase();  // Creates its own dependency
13    }
14    
15    public void createUser(String name) {
16        database.save(name);
17    }
18}
19
20// Problem: Can't switch to PostgreSQL without changing UserService!
21
22// ✅ GOOD: Both depend on abstraction
23interface Database {
24    void save(String data);
25    String find(String id);
26}
27
28class MySQLDatabase implements Database {
29    @Override
30    public void save(String data) {
31        System.out.println("MySQL: Saving " + data);
32    }
33    
34    @Override
35    public String find(String id) {
36        return "MySQL data for " + id;
37    }
38}
39
40class PostgreSQLDatabase implements Database {
41    @Override
42    public void save(String data) {
43        System.out.println("PostgreSQL: Saving " + data);
44    }
45    
46    @Override
47    public String find(String id) {
48        return "PostgreSQL data for " + id;
49    }
50}
51
52class UserService {
53    private final Database database;  // Depends on abstraction!
54    
55    // Dependency injected through constructor
56    public UserService(Database database) {
57        this.database = database;
58    }
59    
60    public void createUser(String name) {
61        database.save(name);
62    }
63}
64
65// Usage - easy to swap implementations
66public class Main {
67    public static void main(String[] args) {
68        // Use MySQL
69        Database mysqlDb = new MySQLDatabase();
70        UserService service1 = new UserService(mysqlDb);
71        
72        // Use PostgreSQL - same UserService code!
73        Database postgresDb = new PostgreSQLDatabase();
74        UserService service2 = new UserService(postgresDb);
75        
76        // Use mock for testing
77        Database mockDb = new MockDatabase();
78        UserService testService = new UserService(mockDb);
79    }
80}

UserService depends on Database interface, not specific implementations. Can easily switch databases or use mocks for testing.

When to Use

  • When you need flexibility in implementations

  • For testability (inject mocks)

  • When working with external services

  • In layered architectures

  • When building frameworks or libraries

Advantages

  • Loose coupling between modules

  • Easy to swap implementations

  • Better testability

  • More flexible and maintainable

  • Supports plugin architectures

Best Practices

  • 1.

    Define interfaces at the boundary of modules

  • 2.

    Use constructor injection for required dependencies

  • 3.

    High-level module owns the interface definition

  • 4.

    Use DI frameworks (Spring, Guice) for complex apps

  • 5.

    Don't create dependencies inside classes

💡 Interview Tips

  • Explain both parts of the principle

  • Show database abstraction example

  • Connect to Dependency Injection

  • Discuss testing benefits (mock injection)

  • Mention DI frameworks (Spring, etc.)

AI Tutor

Ask about the topic

Sign in Required

Please sign in to use the AI tutor

Sign In
Dependency Inversion Principle - SOLID Principles | LLD | Revise Algo