3. Dependency Inversion Principle (DIP)

Dependency inversion, which may be familiar to developers who use the Spring framework, is a design mantra commonly used in Spring applications that requires the following:

  • A high-level module should not depend on A low-level module, and both should depend on its abstraction assuming that AImpl should not depend on BImpl, but that A depends on B
  • Abstractions should not depend on details, but details depend on abstractions

In the Java language, details are implementation classes, concrete implementation details; Abstraction refers to interfaces or abstract classes, which cannot be instantiated

The details of our implementation in the Java language have been rented as interface oriented development (OOD, object-Orented Design), which has the following properties:

  • [X] modules rely on each other through abstraction, so that no direct dependencies between classes occur
  • The [x] interface or abstract class does not depend on the implementation class
  • [X] Implementation classes depend on interfaces or abstract classes

3.1 Cases of dependency inversion

Adopting the DIP principle has the following advantages:

  • Adopting DIP reduces coupling between code and reduces the risk of parallel development
  • Improve code maintainability and readability


3.1.1 Preliminary design scheme

To create a Driver type of car that can drive a BenZ type of car, our initial design might be the following UML design diagram,



public class DIP {



  public static void main(String[] args) {

    // Tom drives his BenZ

    Driver tom = new Driver();

    tom.driver(new BenZ());

  }



  static class Driver {

    public void driver(BenZ benZ) {

      benZ.run(100);

    }

  }



  static class BenZ {

    public void run(int speed) {

      System.out.println("Mercedes is on the way." + speed + "Speed running");

    }

  }

}



/ *

The Mercedes is running at 100

* /


Copy the code


Thinking: 🤔 Based on the above code, the driver Tom may drive BWM, QQ and other cars, how to do?

So, on demand, I can create the BWM class as follows:

  static class BWM {

    public void run(int speed) {

      System.out.println("BWM is working with" + speed + "Speed running");

    }

  }

Copy the code

Public void Driver (BenZ, BenZ, BWM, BenZ, BWM, BenZ, BWM, BWM);

  • The Driver class relies on the BenZ implementation class
  • Need to maintain multiple automotive classes at the same time, the maintenance cost is greatly increased
  • When a Driver is created, it must wait until BenZ is implemented to continue development, which fails to remember unit tests or even run, raising the risk of parallel development

3.1.2 Improvement scheme

According to the requirements of dependency injection, we can know that each class should not rely on details, but should rely on its abstraction. Based on this, we abstract the car driver and the car, the upper layer only depends on abstraction, but not details. The IMPLEMENTATION of UML is as follows:







Let’s abstract the driver and the car:

  interface ICar {



    /** The name of the car */

    String name(a);



    /** The car runs */

    void run(int speed);

  }



  interface IDriver {



    /** Drive ICar to avoid relying on subclasses of its implementation */

    void drive(ICar car, int speed);

  }

Copy the code

Continue to implement its ICar and IDriver implementation details

  static class Driver implements IDriver {



    @Override

    public void drive(ICar iCar, int speed) {

      iCar.run(speed);

    }

  }



  static class BenZ implements ICar {



    @Override

    public String name(a) {

      return "BenZ";

    }



    @Override

    public void run(int speed) {

      System.out.println("Mercedes is on the way." + speed + "Speed running");

    }

  }



  static class BWM implements ICar {



    @Override

    public String name(a) {

      return "BWM";

    }



    @Override

    public void run(int speed) {

      System.out.println("BWM is working with" + speed + "Speed running");

    }

  }

Copy the code

In the process of use, we can not implement the original subclass implementation, but use the abstract class implementation

    IDriver tom = new Driver();

    // Tom can drive BWM

    tom.drive(new BWM(), 100);



    // Tom can also drive BenZ

    tom.drive(new BenZ(), 90);

Copy the code


3.1.3 Impact of DIP on parallel development

After the upper level code logic is modified to depend on its abstraction, the interface or abstract class of the two classes can be formulated, and the direct unit testing of the project can be run independently. TDD (test-driven development) is the highest application of DIP.

3.2 Dependency mode

In combination with Spring’s Bean dependency injection methods, we can know that there are three main methods of dependency injection:

  • [x] Construct injection, which injects dependencies at construct time
  • [x] Setter method injection
  • Injection into the [x] interface method (used in the car example)





3.3 Best Practices

To achieve dependency inversion, there are several essential conditions:

  • [x] As far as possible, each class needs to have an abstract class or interface, which is the basic condition for implementing dependency inversion
  • The surface types of [x] variables (see JVM surface types and actual types) should be interfaces or abstract classes as much as possible
  • [x] No class should derive from a concrete class
  • [x] Try not to duplicate the methods of abstract classes. Classes depend on abstractions. Modifying abstractions will affect the stability of the entire code
  • [x] Combined with LSP(In-type substitution principle)

This article is formatted using MDNICE