Control inversion, dependency injection and dependency inversion are indistinguishable?

Through this article, you will understand

  • What is inversion of control (IoC)? What exactly does "reversal" reverse?
  • What is the relationship between Spring and IOC?
  • What are dependency injection (DI) and Dependency Inversion Principle (DIP)?
  • What is the relationship between IOC, DI and DIP?

1. Control reversal (IoC)

1.1 a typical case

Before introducing "inversion of control", let's look at a piece of code

public class UserServiceTest {
    public static boolean doTest() {
        //Write your own judgment logic here
        return false;
    }

    public static void main(String[] args) {

        if (doTest()) {
            System.out.println("Test succeed.");
        } else {
            System.out.println("Test failed.");
        }
    }
}

As mentioned above, we wrote a test case for a method, including the creation of main method. All processes are controlled by ourselves.

Now there is such a framework. The code is as follows:

public abstract class TestCase {
    public void run() {
        if (doTest()) {
            System.out.println("Test succeed.");
        } else {
            System.out.println("Test failed.");
        }
    }

    public abstract boolean doTest();
}


public class JunitApplication {
    private static final List<TestCase> cases = new ArrayList();

    public static void register(TestCase testCase){
        cases.add(testCase);
    }

    public static void main(String[] args) {
        for(TestCase testCase : cases){
            testCase.run();
        }
    }
}

Using this framework, if we write another test case for UserServiceTest, we only need to inherit TestCase and rewrite the doTest method.

public class UserServiceTestCase extends TestCase{
    @Override
    public boolean doTest() {
        //Write your own judgment logic here
        return false;
    }
    
}

//Register test cases
JunitApplication.register();

After reading this example, I believe readers have understood how convenient this framework has brought to us. At the beginning, we need to add a main method for each test method. Once there are more methods to be tested, it will be very inconvenient. Now the framework has established the basic framework for the operation of the program and preset the embedding point for us. We only need to set the embedding point of the framework and leave the rest of the execution process to the framework.

This is a typical example of "framework implementation control inversion". The "control" here refers to the control of the execution process, "reversal" refers to that we need to manually control the execution of all processes before the framework is generated, and after the framework is generated, there is a framework to execute the execution of the whole large process, and the process control is given to the framework by our "reversal".

1.2 proposal of IOC concept

As early as 1988, Ralph E. Johnson and Brian Foote wrote Designing Reusable Classes The concept of inversion of control was put forward in. They never thought how much trouble these words would cause to Chinese programmers in the future!

Although the Spring framework carries forward the concept of IoC, IoC was born much earlier than Spring, and the concept of IoC was put forward when discussing framework design. As for whether the framework and IoC have chicken or egg first, this question does not mean much to us. ​

When the concept of IoC is ambiguous, tracing the origin may be a good idea for us to fully understand this concept. As for the extension beyond the concept, it is just a trifle. Next, let's experience the two more important paragraphs in the article, which I translated freely.

One important characteristic of a framework is that the methods defined by the user to tailor the framework will often be called from within the framework itself, rather than from the user's application code.

The framework defined by the user is often an important feature of the application, not its own code.

This inversion of control gives frameworks the power to serve as extensible skeletons. The methods supplied by the user tailor the generic algorithms defined in the framework for a particular application. This "inversion of control" makes the framework extensible as the skeleton of a program. Users can customize the preset embedded points in the frame.

IoC is an idea, not the landing of a specific programming technology. The framework using the idea of "control reversal" allows users to "fill in the blank" to a certain extent, and the rest of the operation is left to the framework.

1.3 why is IoC proposed

Almost all programming ideas are based on one purpose - decoupling. How does Ioc solve the coupling problem?

Suppose we have four objects, and the dependencies between them are shown in the figure The translated code is roughly as follows:

class A{
    Object b = new B();
    ...
}

class B{
    Object c = new C();
    Object d = new D();
    ...
}

class C{
    Object d = new D();
}

However, object A really needs object B. this dependency cannot be erased, which means that the coupling relationship cannot be completely removed, but it can be weakened! The idea of IoC is to introduce an IoC container to deal with the dependencies between objects, change from active dependency to passive dependency, reduce the coupling relationship, and change from strong coupling to weak coupling. On the role of IoC container, give you an example in life.

Suppose that three customers purchased goods from four stores respectively. Unfortunately, everyone encountered quality problems. Before the birth of the third-party shopping platform, each customer can only negotiate with each store for claim settlement. At this time, there is a strong coupling relationship between customers and stores. After having a third-party shopping platform, customers can directly complain with the platform and let the platform negotiate with each store. The platform will settle claims for each customer. At this time, there is a loose coupling relationship between customers and stores, because the most tired work is undertaken by the platform. At this time, the role of the platform is similar to IoC container. Finally, take Spring as another example.

From the perspective of large granularity, we do not need to write Servlet after using Spring. The process of calling Servlet is all processed to Spring, which is IoC.

From the perspective of small granularity, we can create objects in Spring in the following two ways

// Mode 1
private MySQLDao dao = new MySQLDaoImpl();

// Mode 2
private MySQLDao dao = (MySQLDao) BeanFactory.getBean("mySQLDao");

Using method 1, there is a strong coupling relationship between the caller of dao object and dao object. Once the MySQL daoimpl source code is lost, the whole project will report an error during compilation.

Using method 2, if we configure the bean mySQLDao in the xml file, if the source code is lost, at most one runtime exception (ClassNotFound error) will be reported, which will not affect the startup of the project.

Spring provides a way to automatically find objects for you in mode 2, which is also IoC, and it is one of the common implementation methods of IoC, which depends on search. The other is dependency injection, which will be introduced later.

1.4 relationship between spring and IoC

Spring is one of the most famous frameworks (none) that implement and carry forward the idea of IoC.

1.5 what is IoC's answer when asked in the interview

"Control inversion" is a programming idea applied in the field of software engineering. It is used by assembler objects to bind coupling objects at run time. The coupling relationship between objects is usually unknown at compile time.

In the traditional programming method, the flow of business logic is determined by the objects with well-established relationship in the application. In the case of "control inversion", the flow of business logic is determined by the object relationship diagram, which is instantiated by the IoC container. This implementation method can also abstract the definition of the association relationship between objects. The binding process is implemented by "dependency injection".

Control reversal is a design paradigm aimed at giving more control to the target components in the application, and has played an effective role in practical work.

2. Dependency injection (DI)

The English translation of Dependency Injection is Dependency Injection, abbreviated as DI.

Dependency injection is not equal to control inversion! Dependency injection is only a way to achieve control inversion! Dependency injection is not equal to control inversion! Dependency injection is only a way to achieve control inversion! Dependency injection is not equal to control inversion! Dependency injection is only a way to achieve control inversion!

This concept is dressed in the coat of "tall", but the essence is very simple. The explanation in human words is: instead of creating dependent class objects inside the class through new(), the dependent class objects are created externally and passed (or injected) to the class through constructors, function parameters, etc.

Let's give an example of usual encoding. When we call Service service in Controller, we usually write this way.

@Api(tags = {"Alarm contact interface"})
@RestController
@RequestMapping("/iot/contact")
public class AlarmContactController extends BaseController {
    
    // This is the famous DI, isn't it very simple!
    @Autowired
    private IAlarmContactService alarmContactService;

    ...

}

This is the famous DI, isn't it very simple!

2.1 how to answer "dependency injection" in the interview

Dependency injection is a means to instantiate the function objects that other objects depend on when the compilation stage does not know which class the required function comes from. There are three implementation methods: constructor injection, setter method injection and interface injection.

3. Dependency Inversion Principle (DIP)

3.1 definitions

The English translation of "Dependency Inversion Principle" is Dependency Inversion Principle, abbreviated as DIP. Chinese translation is sometimes called "dependency reversal" principle.

"Dependence Inversion" is the main content of this paper. It is the second of the seven design principles. It is widely used in production practice. The main content is

  1. High level modules should not directly rely on low-level modules;
  2. High level modules and low-level modules should depend on each other through abstractions;
  3. Abstractions don't rely on concrete implementation details. Concrete implementation details depend on abstractions.

It doesn't matter if you don't understand it for the time being. Let's look at a code case first.

3.2 code examples

The gyroscope has developed an automatic driving system. Under active negotiations, it has reached a cooperation agreement with Honda and Ford. The two manufacturers provide car driving, turning and stopping api for automatic driving. The system can achieve automatic driving. The code is as follows

/**
 * @author The official account [cicada breeze]
 * @desc Interface provided by Ford motor manufacturer
 */
public class FordCar{
    public void run(){
        System.out.println("Ford started");
    }

    public void turn(){
        System.out.println("Ford began to turn");
    }

    public void stop(){
        System.out.println("Ford started parking");
    }
}

/**
 * @author The official account [cicada breeze]
 * @desc Interface provided by Honda automobile manufacturer
 */
public class HondaCar {
    public void run() {
        System.out.println("Honda is starting");
    }

    public void turn() {
        System.out.println("Honda began to turn");
    }

    public void stop() {
        System.out.println("Honda started parking");
    }
}

/**
 * @author The official account [cicada breeze]
 * @desc Autopilot system
 */
public class AutoDriver {
    public enum CarType {
        Ford, Honda
    }

    private CarType type;
    
    private HondaCar hcar = new HondaCar();
    private FordCar fcar = new FordCar();

    public AutoDriver(CarType type) {
        this.type = type;
    }

    public void runCar() {
        if (type == CarType.Ford) {
            fcar.run();
        } else {
            hcar.run();
        }
    }

    public void turnCar() {
        if (type == CarType.Ford) {
            fcar.turn();
        } else {
            hcar.turn();
        }
    }

    public void stopCar() {
        if (type == CarType.Ford) {
            fcar.stop();
        } else {
            hcar.stop();
        }
    }

}

The autopilot system worked well. Soon, Audi and Mercedes Benz and BMW all found gyros for cooperation. The gyro had to change the code to this way.

/**
 * @author The official account [cicada breeze]
 * @desc Autopilot system
 */
public class AutoDriver {
    public enum CarType {
        Ford, Honda, Audi, Benz, Bmw
    }

    private CarType type;

    private HondaCar hcar = new HondaCar();
    private FordCar fcar = new FordCar();
    private AudiCar audicar = new AudiCar();
    private BenzCar benzcar = new BenzCar();
    private BmwCar bmwcar = new BmwCar();

    public AutoDriver(CarType type) {
        this.type = type;
    }

    public void runCar() {
        if (type == CarType.Ford) {
            fcar.run();
        } else if (type == CarType.Honda) {
            hcar.run();
        } else if (type == CarType.Audi) {
            audicar.run();
        } else if (type == CarType.Benz) {
            benzcar.run();
        } else {
            bmwcar.run();
        }
    }

    public void turnCar() {
        if (type == CarType.Ford) {
            fcar.turn();
        } else if (type == CarType.Honda) {
            hcar.turn();
        } else if (type == CarType.Audi) {
            audicar.turn();
        } else if (type == CarType.Benz) {
            benzcar.turn();
        } else {
            bmwcar.turn();
        }
    }

    public void stopCar() {
        if (type == CarType.Ford) {
            fcar.stop();
        } else if (type == CarType.Honda) {
            hcar.stop();
        } else if (type == CarType.Audi) {
            audicar.stop();
        } else if (type == CarType.Benz) {
            benzcar.stop();
        } else {
            bmwcar.stop();
        }
    }

}

If you read my last article Opening and closing principle You will immediately realize that this code does not comply with the opening and closing principle. Yes, a piece of code may not conform to multiple design principles at the same time. What is the problem with today's "dependency inversion" principle?

Let's take another look at the requirements of the "dependency inversion" principle:

  1. High level modules should not directly rely on low-level modules;
  2. High level modules and low-level modules should depend on each other through abstractions;
  3. Abstractions don't rely on concrete implementation details. Concrete implementation details depend on abstractions.

For the first point, the high-level module AutoDriver directly relies on the low-level module XXCar, which is reflected in the direct new of specific automobile objects in AutoDriver. Therefore, points 2 and 3 have not been achieved. UML class diagram is as follows: Let's add a layer of abstraction between the upper module and the lower module. Define an interface ICar to represent the abstract car. In this way, AutoDriver directly depends on the abstract ICar. Look at the code:

/**
 * @author The official account [cicada breeze]
 * @desc Abstract interface of automobile
 */
public interface ICar {
    void run();
    void turn();
    void stop();
}

public class FordCar implements ICar{
    @Override
    public void run(){
        System.out.println("Ford started");
    }
    
    @Override
    public void turn(){
        System.out.println("Ford began to turn");
    }
    
    @Override
    public void stop(){
        System.out.println("Ford started parking");
    }
}

public class HondaCar implements ICar{
    @Override
    public void run() {
        System.out.println("Honda is starting");
    }

    @Override
    public void turn() {
        System.out.println("Honda began to turn");
    }

    @Override
    public void stop() {
        System.out.println("Honda started parking");
    }
}

public class AudiCar implements ICar{
    @Override
    public void run() {
        System.out.println("Audi is starting");
    }

    @Override
    public void turn() {
        System.out.println("Audi began to turn");
    }

    @Override
    public void stop() {
        System.out.println("Audi started parking");
    }
}

public class BenzCar implements ICar{
    @Override
    public void run() {
        System.out.println("Mercedes Benz starts");
    }

    @Override
    public void turn() {
        System.out.println("Mercedes began to turn");
    }

    @Override
    public void stop() {
        System.out.println("Mercedes began to stop");
    }
}

public class BmwCar implements ICar {
    @Override
    public void run() {
        System.out.println("BMW is starting");
    }

    @Override
    public void turn() {
        System.out.println("BMW began to turn");
    }

    @Override
    public void stop() {
        System.out.println("BMW started parking");
    }
}

/**
 * @author The official account [cicada breeze]
 * @desc Autopilot system
 */
public class AutoDriver {

    private ICar car;

    public AutoDriver(ICar car) {
        this.car = car;
    }

    public void runCar() {
        car.run();
    }

    public void turnCar() {
        car.turn();
    }

    public void stopCar() {
        car.stop();
    }

}

After the reconstruction, we found that the high-level module AutoDriver directly depends on the abstract ICar rather than XXXCar, so that even if more automobile manufacturers join the cooperation, there is no need to modify the AutoDriver. This is the dependency between high-level modules and low-level modules through abstraction.

In addition, ICar does not rely on XXXCar, because ICar is an abstraction defined by high-level modules. If automobile manufacturers want to reach cooperation, they must follow the standard defined by AutoDriver, that is, they need to implement the interface of ICar, which is that the specific details mentioned in Article 3 depend on abstraction!

Let's take a look at the UML diagram after refactoring

It can be seen that originally AutoDriver directly points to XXXCar, now AutoDriver directly points to abstract ICar, and various XXXCar objects point to ICar in turn, which is the so-called "dependency inversion".

Seeing this, I wonder if you have a deep understanding of the principle of "Dependence Inversion". In fact, the idea of adding an abstraction layer in the middle is widely used. Let me give two examples.

3.3 ubiquitous abstraction

3.3.1 abstraction of JVM

Although the JVM is called Java virtual machine, the operation of its underlying code does not directly depend on the Java language, but defines a bytecode abstraction (industry standard). As long as the bytecode standard is implemented, any language can run on the JVM.

3.3.2 birth of money

Back in the era of barter, Wang Er wanted to exchange his spare chicken for a pair of straw sandals, Li Si wanted to exchange his spare straw sandals for a pair of trousers, and Zhao Wu wanted to exchange his spare trousers for a hat... If we continue to exchange things, this circle will go around grandma's house. Then people abstract the middle layer - money. Money as the standard of purchasing power makes the exchange of goods more convenient. ​

4. Recommended reading

5. References

Keywords: Java Spring Autonomous vehicles

Added by advancedfuture on Sat, 12 Feb 2022 00:20:58 +0200