写点什么

设计模式之美––依赖反转

作者:GalaxyCreater
  • 2023-03-26
    广东
  • 本文字数:2493 字

    阅读完需:约 8 分钟

控制反转(Inversion Of Control,IOC)


控制反转是一种笼统的设计思想,用来指导框架层面的设计。

“控制”指的是对程序执行流程的控制;

“反转”指的是在没有使用框架之前,程序员自己控制整个程序的执行。在使用框架之后,整个程序的执行流程可以通过框架来控制。流程的控制权从程序员“反转”到了框架


通过框架来实现“控制反转”的例子。框架提供了一个可扩展的代码骨架,用来组装对象、管理整个执行流程。程序员利用框架进行开发的时候,只需要往预留的扩展点上,添加跟自己业务相关的代码,就可以利用框架来驱动整个程序流程的执行。


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(); } }
// 不同的测试类,只需要实现doTest方法,即可利用测试框架的逻辑run(),不需要改动其他地方的代码public class UserServiceTest extends TestCase { @Override public boolean doTest() { // ... }}
// 注册操作还可以通过配置的方式来实现,不需要程序员显示调用register()JunitApplication.register(new UserServiceTest();
复制代码


依赖注入(Dependency Injection, DI)

依赖注入和控制返回刚好相反,是一种具体的编码技巧。这个概念听起来很“高大上”,实际上,理解、应用起来非常简单。


依赖注入一句话概括:不通过 new() 的方式在类内部创建依赖类对象,而是将依赖的类对象在外部创建好之后,通过构造函数、函数参数等方式传递(或注入)给类使用。


非依赖注入例子

// 非依赖注入实现方式public class Notification {  private MessageSender messageSender;    public Notification() {    this.messageSender = new MessageSender(); // 内部创建对象  }}
复制代码


依赖注入例子 1--在外部创建对象

// 依赖注入的实现方式public class Notification {  private MessageSender messageSender;    // 通过构造函数将messageSender传递进来  public Notification(MessageSender messageSender) {    this.messageSender = messageSender;  }}
//使用NotificationMessageSender messageSender = new MessageSender();Notification notification = new Notification(messageSender);
复制代码

依赖注入例子 2--基于接口而非实现编程


public class Notification { private MessageSender messageSender; /* 通过依赖注入的方式来将依赖的类对象传递进来,这样就提高了代码的扩展性,从而可以灵活地替换依赖的类 */ public Notification(MessageSender messageSender) { this.messageSender = messageSender; }}
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) { //.... }}
//使用NotificationMessageSender messageSender = new SmsSender();Notification notification = new Notification(messageSender);
复制代码


依赖注入框架(DI Framework)

一些项目可能会涉及几十、上百、甚至几百个类,如果所有都用代码来完成,那成本比较高,所以将依赖注入的操作,不由程序员自己写,由依赖注入框架实现。

// 这部分代码由依赖注入框架实现,如spring boot的@AutowriedMessageSender messageSender = new SmsSender();   Notification notification = new Notification(messageSender);
复制代码


依赖反转原则(Dependency Inversion Principle, DIP)


又叫依赖倒置原则


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.


高层模块(high-level modules)不要依赖低层模块(low-level)。高层模块和低层模块应该通过抽象(abstractions)来互相依赖。除此之外,抽象(abstractions)不要依赖具体实现细节(details),具体实现细节(details)依赖抽象(abstractions)。


高层模块:指调用者

底层模块:指被调用者


适用场景

平时业务开发中,高层模块依赖底层模块是没问题的,这条原则主要用于指导框架层面设计。


例子

Tomcat 是运行 Java Web 应用程序的容器。我们编写的 Web 应用程序代码只需要部署在 Tomcat 容器下,便可以被 Tomcat 容器调用执行。按照之前的划分原则,Tomcat 就是高层模块,我们编写的 Web 应用程序代码就是低层模块。Tomcat 和应用程序代码之间并没有直接的依赖关系,两者都依赖同一个“抽象”,也就是 Servlet 规范。Servlet 规范不依赖具体的 Tomcat 容器和应用程序的实现细节,而 Tomcat 容器和应用程序依赖 Servlet 规范。

控制反转就是一种钩子、插件的思维。 我们规定好具体的流程,在这个流程中会开放出一些类(在函数式的语言中可能是某种类型的可调用对象,粒度可以被细化到函数级别),我们只需要遵循这些开放出来的规范编写代码,然后进行一定的注册就可以把我们的逻辑挂到这个流程的某个阶段。 Tomcat 是一个 HTTP 服务器,其内部规定好了如何处理 HTTP 请求的报文,它给我们开放出来的规范把就是 HTTP 报文转换成 ServletRequest,把响应转换为 ServletResponse 之后的 Servlet 类中的 API,我们只需要编写自己的 Servlet 然后把这些 Servlet 注册到容器中,对应的 HTTP 请求就可以被派发到我们的 Servlet 对象中了


发布于: 刚刚阅读数: 3
用户头像

GalaxyCreater

关注

还未添加个人签名 2019-04-21 加入

还未添加个人简介

评论

发布
暂无评论
设计模式之美––依赖反转_设计模式_GalaxyCreater_InfoQ写作社区