Small knowledge, big challenge! This article is participating in the creation activity of “Essential Tips for Programmers”.

How dependency injection is understood and how the concepts of inversion of control and dependency injection differ. How is IOC in the Spring framework different from these concepts

Inversion of control

Inversion of Control, or IOC, is a concept often mentioned in Spring. Consider an example

public class UserServiceTest {
    public static boolean doTest(a){
        // ...
        return true;
    }

    public static void main(String[] args) {
        if (doTest()){
            System.out.println("success");
        }else {
            System.out.println("fail"); }}}Copy the code

In the above code, all the flow is controlled by the programmer, in fact, can carry out a certain abstraction, using the framework to achieve the same function, as shown in the following code

public abstract class TestCase {
    public void run(a){
        if (doTest()){
            System.out.println("success");
        }else {
            System.out.println("fail"); }}public abstract Boolean doTest(a);
}

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

   public static void doRegister(TestCase testCase){
       testCaseList.add(testCase);
   }

    public static void main(String[] args) {
        for(TestCase testCase:testCaseList){ testCase.run(); }}}Copy the code

Using this simplified version of the project, you only need to fill in the specific code in the framework reserved extension point (doTest abstract function in TestCase) to implement the previous function, no need to write the main function, as shown below

public class UserServiceTest extends TestCase{
    @Override
    public Boolean doTest(a) {

        return true;
    }

    public  UserServiceTest(a){
        // The register operation can also be configured without requiring the programmer to display a call to register()
        TestApplication.doRegister(newUserServiceTest()); }}Copy the code

As an example of inversion of control, the framework provides an extensible code skeleton for assembling objects and managing execution flows. Developers only need to add their own business-related code to the reserved extension points, and can use the framework to drive the execution of the entire program flow.

Here “control” refers to the control of the program execution flow, and “reverse” refers to the developer’s own control of the entire program execution flow before the use of the framework, after the use of the framework to control the entire utilization by the framework. Inversion of control is not a specific implementation technique, but rather a general idea that is used to guide the top-level design of frameworks

Dependency injection

Dependency injection is the opposite of inversion of control; it is a specific coding technique. In a nutshell: instead of creating dependent objects inside a class using new(), create dependent objects outside the class and pass them to the class using constructors, function arguments, and so on

Non-dependency injection

public class Notification {
    private MessageSender messageSender;

    public Notification(a){
        this.messageSender = new MessageSender();
    }

    public void sendMessage(a){ messageSender.sendMessage(); }}public class MessageSender {
    public void sendMessage(a){
        //....}}Copy the code

Dependency injection

public class Notification {
    private MessageSender messageSender;

    public Notification(MessageSender messageSender){
        this.messageSender = messageSender;
    }

    public void sendMessage(a){
        this.messageSender.sendMessage(); }}Copy the code

The dependency injection method is used to pass in the dependent class object, which improves the expansibility of the code and can flexibly replace the dependent class, which is also more in line with the open and closed principle. We can also define MessageSender as an interface, based on the interface rather than implementation class programming, as follows

public class Notification {
    private MessageSender messageSender;

    public Notification(MessageSender messageSender){
        this.messageSender = messageSender;
    }

    public void sendMessage(a){
        this.messageSender.sendMessage(); }}public interface MessageSender {
     void sendMessage(a);
}

public class SmsSender implements MessageSender{
    @Override
    public void sendMessage(a) {}}Copy the code

The example above is dependency injection, which is often used in everyday development

Dependency injection framework

In Notification, there is no need to create MessageSender objects with new inside the class. However, the creation and assembly of MessageSender objects are simply moved to higher levels of code

public class Demo {
    public static void main(String[] args) {
        MessageSender smsSender = new SmsSender();
        Notification notification = newNotification(smsSender); notification.sendMessage(); }}Copy the code

In real software development, projects may involve hundreds of classes, and the creation and dependency of class objects can become very troublesome, so the work like object creation and dependency injection itself has nothing to do with business and can be abstracted into a framework to be done automatically

This framework is the dependency Injection framework. With the extension points provided by the dependency injection framework, we simply configure all the class objects that need to be created, and the dependencies between classes, so that the framework can automatically create objects, manage the life cycle of objects, dependency injection, and other things that should be done by programmers.

In fact, there are many dependency injection frameworks out there, most notably Spring

Dependency inversion principle

I’ve covered inversion of control, dependency injection, dependency injection frameworks, and finally the dependency inversion principle. The English translation of Dependency Inversion Principle is Dependency Inversion Principle (DIP). Chinese translation is sometimes called dependency inversion principle.

The main concept is: high-level modules do not depend on low-level modules. High-level and low-level modules should depend on each other through abstractions. In addition, abstractions should not rely on details. Details should rely on abstractions. The so-called division of high-level module and low-level module, simply speaking, in the call chain, the caller belongs to the high-level, the called belongs to the low-level. In normal business code development, there is no problem with higher-level modules relying on lower-level modules. In fact, this principle is mainly used to guide frame-level design, similar to the inversion of control mentioned earlier. Let’s take Tomcat, the Servlet container, as an example.

Tomcat is a container for running Java Web applications. The Web application code we wrote only needs to be deployed under the Tomcat container to be invoked and executed by the Tomcat container. According to the previous classification, Tomcat is the high-level module, and the Web application code we wrote is the low-level module. There is no direct dependency between Tomcat and application code; both rely on the same “abstraction”, the Sevlet specification. The Servlet specification does not depend on the implementation details of specific Tomcat containers and applications, which depend on the Servlet specification.

Not to smell is not to smell, not to see, not to see, not to know