Geek Time-The Beauty of Design Patterns Theory Ten: Inversion of Control, Dependency Inversion, and Dependency Injection. What are the differences and connections among the three?

Dependence reversal principle

● The concept of "dependency inversion" refers to the "what dependence" of "who and whom" has been reversed? How to understand the word "reverse"?

● We often hear two other concepts: "inversion of control" and "dependency injection". What is the difference and connection between these two concepts and "dependency inversion"? Are they talking about the same thing?

● If you are familiar with the Java language, what does the IOC in the Spring framework have to do with these concepts?

Inversion of Control (IOC)

Before talking about the "dependency inversion principle", let's talk about "inversion of control" first. The English translation of Inversion Of Control is Inversion Of Control, abbreviated as IOC. Here I want to emphasize that if you are a Java engineer, don't associate this "IOC" with the IOC of the Spring framework for the time being. Regarding Spring's IOC, we will talk about it later.

Let's take an example to see what is inversion of control.


public class UserServiceTest {
    
    
  public static boolean doTest() {
    
    
    // ... 
  }
  
  public static void main(String[] args) {
    
    //这部分逻辑可以放到框架中
    if (doTest()) {
    
    
      System.out.println("Test succeed.");
    } else {
    
    
      System.out.println("Test failed.");
    }
  }
}

In the above code, all processes are controlled by the programmer. If we abstract a framework like the following, let's look at how to use the framework to achieve the same function. The specific code implementation 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> testCases = new ArrayList<>();
  
  public static void register(TestCase testCase) {
    
    
    testCases.add(testCase);
  }
  
  public static final void main(String[] args) {
    
    
    for (TestCase case: testCases) {
    
    
      case.run();
    }
  }

After introducing this simplified version of the test framework into the project, we only need to fill in the specific test code in the extension point reserved by the framework, which is the doTest() abstract function in the TestCase class, to achieve the previous functions. There is no need to write the main() function responsible for executing the process at all. The specific code is as follows:


public class UserServiceTest extends TestCase {
    
    
  @Override
  public boolean doTest() {
    
    
    // ... 
  }
}

// 注册操作还可以通过配置的方式来实现,不需要程序员显示调用register()
JunitApplication.register(new UserServiceTest();

The example just cited is a typical example of implementing "inversion of control" through a framework. The framework provides an extensible code skeleton to assemble objects and manage the entire execution process. When programmers use the framework for development, they only need to add code related to their own business to the reserved extension points, and they 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 "reversal" refers to the programmer's own control of the execution of the entire program before the framework is used. After using the framework, the execution flow of the entire program can be controlled by the framework. The control of the process has been "reversed" from the programmer to the framework.

In fact, there are many ways to achieve inversion of control. In addition to the method similar to the template design pattern shown in the example just now, there are also methods such as dependency injection that will be mentioned soon. Therefore, inversion of control is not a kind of The specific implementation skills are a relatively general design idea, which is generally used to guide the design of the framework level.

Dependency Injection (DI)

Next, let's look at dependency injection. Dependency injection is the opposite of inversion of control, it is a specific coding technique. The English translation of dependency injection is Dependency Injection, abbreviated as DI. For this concept, there is a very vivid statement, that is: dependency injection is a concept with a price tag of $25, which is actually only worth 5 cents. In other words, this concept sounds very "tall", but in fact, it is very simple to understand and apply.

So what exactly is dependency injection? We use one sentence to summarize: instead of creating dependent class objects inside the class through new(), but after creating the dependent class objects externally, they are passed (or injected) to them through constructors, function parameters, etc. Class use.

Let us explain through an example. In this example, the Notification class is responsible for message push, relying on the MessageSender class to push product promotions, verification codes and other messages to users. Let's use dependency injection and non-dependency injection to achieve it. The specific implementation code is as follows:


// 非依赖注入实现方式
public class Notification {
    
    
  private MessageSender messageSender;
  
  public Notification() {
    
    
    this.messageSender = new MessageSender(); //此处有点像hardcode
  }
  
  public void sendMessage(String cellphone, String message) {
    
    
    //...省略校验逻辑等...
    this.messageSender.send(cellphone, message);
  }
}

public class MessageSender {
    
    
  public void send(String cellphone, String message) {
    
    
    //....
  }
}
// 使用Notification
Notification notification = new Notification();

// 依赖注入的实现方式
public class Notification {
    
    
  private MessageSender messageSender;
  
  // 通过构造函数将messageSender传递进来
  public Notification(MessageSender messageSender) {
    
    
    this.messageSender = messageSender;
  }
  
  public void sendMessage(String cellphone, String message) {
    
    
    //...省略校验逻辑等...
    this.messageSender.send(cellphone, message);
  }
}
//使用Notification
MessageSender messageSender = new MessageSender();
Notification notification = new Notification(messageSender);

The dependent class objects are passed in through dependency injection, which improves the scalability of the code, and we can flexibly replace the dependent classes. This point was also mentioned when we talked about the "opening and closing principle" before. Of course, the above code still has room for further optimization. We can also define MessageSender as an interface, based on the interface instead of implementing programming. The transformed code is as follows:


public class Notification {
    
    
  private MessageSender messageSender;
  
  public Notification(MessageSender messageSender) {
    
    
    this.messageSender = messageSender;
  }
  
  public void sendMessage(String cellphone, String message) {
    
    
    this.messageSender.send(cellphone, message);
  }
}

public interface MessageSender {
    
    
  void send(String cellphone, String message);
}

// 短信发送类
public class SmsSender implements MessageSender {
    
    
  @Override
  public void send(String cellphone, String message) {
    
    
    //....
  }
}

// 站内信发送类
public class InboxSender implements MessageSender {
    
    
  @Override
  public void send(String cellphone, String message) {
    
    
    //....
  }
}

//使用Notification
MessageSender messageSender = new SmsSender();
Notification notification = new Notification(messageSender);

In fact, you only need to master the example just given, which is equivalent to fully mastering dependency injection. Although dependency injection is very simple, it is very useful. In later chapters, we will talk about it as the most effective way to write testable code.

Dependency Injection Framework (DI Framework)

After understanding what "dependency injection" is, let's take a look at what is "dependency injection framework". We still borrow the example just now to explain.

In the Notification class implemented by dependency injection, although we don’t need to use a hard code-like approach to create a MessageSender object through new inside the class, the work of creating and assembling (or injecting) objects is just moved to The higher-level code is only, we still need our programmers to implement it. The specific code is as follows:


public class Demo {
    
    
  public static final void main(String args[]) {
    
    
    MessageSender sender = new SmsSender(); //创建对象
    Notification notification = new Notification(sender);//依赖注入
    notification.sendMessage("13918942177", "短信验证码:2346");
  }
}

In actual software development, some projects may involve dozens, hundreds, or even hundreds of classes, and the creation of class objects and dependency injection will become very complicated. If this part of the work is done by programmers writing their own code, it is prone to errors and the development cost is relatively high. The work of object creation and dependency injection has nothing to do with specific business. We can abstract it into a framework to complete it automatically.

As you might have guessed, this framework is the "dependency injection framework". We only need to use the extension points provided by the dependency injection framework to simply configure all the class objects that need to be created, the dependencies between classes and classes, and then the framework can automatically create objects, manage the life cycle of objects, dependency injection, etc. Things that originally required programmers to do.

In fact, there are many existing dependency injection frameworks, such as Google Guice, Java Spring, Pico Container, Butterfly Container, etc. However, if you are familiar with Java Spring framework, you might say, Spring Framework himself claims to be the Inversion of Control container (Inversion Of Control Container).

In fact, neither statement is wrong. It's just that the expression of the inversion of control container is a very broad description, and the expression of the DI dependency injection framework is more specific and targeted. Because we mentioned earlier that there are many ways to achieve inversion of control, in addition to dependency injection, there are template patterns, etc., and the inversion of control of the Spring framework is mainly achieved through dependency injection. However, this distinction is not very obvious, and it is not very important, you just need to understand it a little bit.

Dependency Inversion Principle (DIP)

I talked about inversion of control, dependency injection, and dependency injection frameworks. Now, let’s talk about today’s protagonist: the principle of dependency inversion. The English translation of Dependency Inversion Principle is Dependency Inversion Principle, abbreviated as DIP. Chinese translation is sometimes called the principle of dependency inversion.

In order to trace the origin, I first give the most original English description of this principle:

High-level modules shouldn’t depend on low-level modules. Both modules
should depend on abstractions. In addition, abstractions shouldn’t
depend on details. Details depend on abstractions.

We translate it into Chinese, which roughly means: high-level modules do not depend on low-level modules. High-level modules and low-level modules should rely on each other through abstractions. In addition, abstractions should not depend on specific implementation details (details), and specific implementation details (details) depend on abstractions.

The so-called division of high-level modules and low-level modules is simply that in the call chain, the caller belongs to the high level and the callee belongs to the low level. In the usual business code development, there is no problem with high-level modules relying on low-level modules. In fact, this principle is mainly used to guide the design of the framework level, similar to the inversion of control mentioned earlier. Let's take the Tomcat Servlet container as an example to explain.

Tomcat is a container for running Java Web applications. The web application code we write only needs to be deployed under the Tomcat container, and then it can be invoked and executed by the Tomcat container. According to the previous division principle, Tomcat is the high-level module, and the web application code we write is the low-level module. There is no direct dependency between Tomcat and application code. Both rely on the same "abstraction", which is the Servlet specification. The Servlet specification does not depend on the implementation details of the specific Tomcat container and application, while the Tomcat container and application rely on the Servlet specification.

Key review

1. Inversion of Control

In fact, inversion of control is a relatively general design idea, not a specific implementation method, and is generally used to guide the design of the framework level. The "control" mentioned here refers to the control of the program execution flow, and the "reversal" refers to the programmer's own control of the execution of the entire program before the framework is used. After using the framework, the execution flow of the entire program is controlled by the framework. Control of the process is "reversed" from the programmer to the framework.

2. Dependency Injection

Dependency injection and inversion of control are the opposite, it is a specific coding technique. We don't create dependent class objects inside the class through the new method, but after the dependent class objects are created externally, they are passed (or injected) to the class for use through constructors, function parameters, etc.

3. Dependency Injection Framework

Through the extension points provided by the dependency injection framework, we simply configure all the required classes and their dependencies between classes, and then the framework can automatically create objects, manage the life cycle of objects, dependency injection, etc. originally required programmers Things to do.

4. Dependence Reversal Principle

The principle of dependency inversion is also called the principle of dependency inversion. This principle is similar to inversion of control and is mainly used to guide the design of the framework level. High-level modules do not depend on low-level modules, they all rely on the same abstraction. Abstraction should not depend on specific implementation details. Specific implementation details depend on abstraction.

Guess you like

Origin blog.csdn.net/zhujiangtaotaise/article/details/110439769