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