DIP - Dependency Inversion Principle

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

The Dependency Inversion Principle (DIP) states that:

  • High-level modules should not depend on low-level modules. Both should depend on abstractions.

  • Abstractions should not depend upon details. Details should depend upon abstractions.

The Dependency Injection (DI) Pattern helps achieve the Dependency Inversion Principle.

With normal coding, high-level modules call low-level modules. The high-level module will depend and the low-level module generates the dependencies. When the low-level module changes, the high-level module must also change. One change creates a cascade of changes, reducing the maintainability of the code.

If the principle of dependency inversion is followed, then high-level modules will depend on the interface, not the implementation. Instead of high-level instantiating the dependency implemented by low-level modules, this principle states that the high-level module defines interfaces, then the low-level module implements those interfaces. The high-level doesn’t need to worry about when and where to instantiate the dependency. The main program will inject the dependency into the high-level instead of the high-level instantiating the dependency. The injection can be done using the constructors. Therefore, we can easily replace and modify the low-level module without affecting the high-level module.

Example

interface DBConnection {
    void connect();
}

class OracleConnection implements DBConnection {
    @Override
    public void connect() {
        System.out.println("Oracle connected");
    }
}

class DbConnectionFactory {
    private OracleConnection dbConnection; // depend on implementation - violate DIP

    public DatabaseConfig(OracleConnection dbConnection) {
        this.dbConnection = dbConnection;
        this.dbConnection.connect();
    }

    public DBConnection getConnection() {
        return this.dbConnection;
    }
}

class DIPExample {

    public static void main(String[] args) {
        OracleConnection conn = new OracleConnection();
        DbConnectionFactory factory = new DbConnectionFactory(conn);
    }
}

The program above works fine. However, now we want to change the database to PostgreSQL. We have to modify the DbConnectionFactory. It's not good.

Let's refactor the code to depend on the abstraction (interface DBConnection).

interface DBConnection {
    void connect();
}

class OracleConnection implements DBConnection {
    @Override
    public void connect() {
        System.out.println("Oracle connected");
    }
}

class PostgreSQLConnection implements DBConnection {
    @Override
    public void connect() {
        System.out.println("PostgreSQL connected");
    }
}

class DbConnectionFactory {
    private OracleConnection dbConnection;

    public DatabaseConfig(OracleConnection dbConnection) {
        this.dbConnection = dbConnection;
        this.dbConnection.connect();
    }

    public DBConnection getConnection() {
        return this.dbConnection;
    }
}

class DbConnectionFactory {
    private final DBConnection dbConnection; // depend on abstractions
 
    public DbConnectionFactory(DBConnection dbConnection) {
        this.dbConnection = dbConnection;
        this.dbConnection.connect();
    }
 
    public DBConnection getConnection() {
        return this.dbConnection;
    }
}
 
class DIPExample {
 
    public static void main(String[] args) {
        DBConnection conn = new OracleConnection();
        DbConnectionFactory factory = new DbConnectionFactory(conn);
        
        // We can inject any DB connection without modifying existing classes
        conn = new PostgreSQLConnection();
        factory = new DbConnectionFactory(conn); 
    }
}

As you can see, our modules are not dependent on each other. Changing the code of one module does not affect the other modules. If you want to support SQLServer, only create a new class and implement DbConnection. If you want to change the connection to SQLServer, only change the config in one class.

Last updated