【设计模式之美】 SOLID 原则之五:依赖反转原则:将代码执行流程交给框架

2024-01-09

一. 控制反转(IOC)

通过一个例子来看一下,什么是控制反转。

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.");
    }
  }
}

改造为框架来实现同样的功能,如下:

// 将流程控制逻辑放到testcase类中,形成一个小的流程框架

//1. 所有的逻辑类都继承这个类,抽象:以便将创建流程框架
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 {
//2. 通过组合的方式 ,统一执行所有的逻辑类
  private static final List<TestCase> testCases = new ArrayList<>();
  public static void register(TestCase testCase) {
    testCases.add(testCase);
  }
  
  // main在框架中写一遍,即可执行所有的逻辑类
  public static final void main(String[] args) {
    for (TestCase case: testCases) {
      case.run();
    }
  }

把这个简化版本的测试框架引入到工程中之后,我们只需要在框架预留的扩展点,也就是 TestCase 类中的 doTest() 抽象函数中,填充具体的测试代码就可以实现之前的功能了,完全不需要写负责执行流程的 main() 函数了。

具体的代码如下所示:

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

// 通过传参的方式将自己的业务代码放到框架中,框架管理执行流程
JunitApplication.register(new UserServiceTest();

刚刚举的这个例子,就是典型的通过框架来实现“控制反转”的例子。有两点:

  1. 框架提供了一个可扩展的 代码骨架,用来组装对象、管理整个执行流程
  2. 程序员利用框架进行开发的时候, 只需要往预留的扩展点上,添加跟自己业务相关的代码,就可以利用框架来驱动整个程序流程的执行。

这里的“控制”指的是对 程序执行流程的控制 ,而“反转”指的是在没有使用框架之前,程序员自己控制整个程序的执行。

控制反转用于指导架构层面的设计

实际上,实现控制反转的方法有很多,除了刚才例子中所示的类似于模板设计模式的方法之外,还有马上要讲到的依赖注入等方法,所以,控制反转并不是一种具体的实现技巧,而是一个比较笼统的设计思想,一般用来指导框架层面的设计。

二. 依赖注入(DI)

依赖注入跟控制反转恰恰相反,它是一种具体的编码技巧。

用一句话来概括DI含义:

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

举例说明:

// 1. 非依赖注入实现方式
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();




// 2. 依赖注入的实现方式
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);

通过依赖注入的方式来将依赖的类对象传递进来,这样就提高了代码的扩展性,我们可以灵活地 替换依赖的类。

优化:基于接口编程
可以基于自己的需求来传递自己想通知的方式(短信、站内),提高代码灵活性和易拓展性。

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);

实际上,你只需要掌握刚刚举的这个例子,就等于完全掌握了依赖注入 。尽管依赖注入非常简单,但却非常有用,它是编写可测试性代码最有效的手段。

三. 依赖注入框架(DI Framework)

什么是“依赖注入框架”。继续借用刚刚的例子来解释。

上述例子还是存在需要手动new的情况

在采用依赖注入实现的 Notification 类中,虽然我们不需要用类似 hard code 的方式,在类内部通过 new 来创建 MessageSender 对象,但是,这个创建对象、组装(或注入)对象的工作仅仅是被移动到了更上层代码而已,还是需要我们程序员自己来实现。

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

通过框架来自动化创建对象:

实际上,现成的依赖注入框架有很多,比如 Google Guice、Java Spring、Pico Container、Butterfly Container 等。

四. 依赖反转原则(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.

高层模块不要依赖低层模块。高层模块和低层模块应该通过抽象来互相依赖。除此之外,抽象不要依赖具体实现细节,具体实现细节依赖抽象。

所谓高层模块和低层模块的划分,简单来说就是,在调用链上, 调用者属于高层,被调用者属于低层

实际上,这条原则主要还是 用来指导框架层面的设计,跟前面讲到的控制反转类似 。我们拿 Tomcat 这个 Servlet 容器作为例子来解释一下。

Tomcat 是运行 Java Web 应用程序的容器。我们编写的 Web 应用程序代码只需要部署在 Tomcat 容器下,便可以被 Tomcat 容器调用执行。

  1. 按照之前的划分原则,Tomcat 就是高层模块,我们编写的 Web 应用程序代码就是低层模块。
  2. Tomcat 和应用程序代码之间并没有直接的依赖关系,两者都依赖同一个“抽象”,也就是 Servlet 规范。
  3. Servlet 规范不依赖具体的 Tomcat 容器和应用程序的实现细节,而 Tomcat 容器和应用程序依赖 Servlet 规范。

参考:《设计模式之美》-- 王争

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【设计模式之美】 SOLID 原则之五:依赖反转原则:将代码执行流程交给框架 的相关文章

  • Hibernate OneToMany 关系是 PersistentBag 而不是 List

    我正在 javafx 中开发一个应用程序 它通过 RMI 与 EAR 连接 该 EAR 连接到 SQLServer DB 并使用 hibernate 映射 POJOS 这些 POJOS 包含双向 OneToMany 和 ManyToOne
  • 在 Kotlin 中实现返回 Collection 的 Java 方法

    我将 Kotlin 与 Spring Security 结合使用 实现该方法时 public interface UserDetails extends Serializable Collection
  • V8 如何管理它的堆?

    我知道V8的垃圾收集在工作时 会从GC的root开始追踪 这样无法到达的对象就会被标记然后被清除 我的问题是GC是如何遍历那些对象的 必须有一个数据结构来存储所有可达或不可达的对象 位图 链接表 顺便说一句 JVM 也做同样的事情吗 艾伦秀
  • JPanel透明背景和显示元素[重复]

    这个问题在这里已经有答案了 我插入一个背景图e 变成 aJPanel但一些界面元素消失了 以下 Java Swing 元素不会出现 标签标题 标签 usuario 标签 密码 按钮加速器 你能否使图像透明或元素不透明 setOpaque f
  • Java Swing:清除JList而不触发监听器

    我的情况如下 我有一个 JList 只要在列表中进行选择 它就会触发搜索 使用 ListSelectionListener 我正在尝试使用以下命令重置列表上的选择list clearSelection 这样做的问题是使用clearSelec
  • 以编程方式将 PEM 证书导入 Java KeyStore

    我有一个由两个文件 crt 和 key 组成的客户端证书 我希望将其导入到 java KeyStore 中 然后在 SSLContext 中使用 以通过 Apache 的 HTTPClient 发送 HTTP 请求 但是 我似乎找不到一种以
  • Spring webflow 应用程序:HTTP 302 暂时移动

    我的 java 应用程序中的每个请求都会生成另外 2 个带有 HTTP 302 错误的请求 例如 如果请求查看名为板 html 这个请求是从首页 html 我收到按以下顺序生成的 3 个请求 POST home html 302 Moved
  • Java/JAXB:将具有相同名称但不同属性值的 XML 元素解组到不同的类成员

    我正在尝试根据其属性之一将具有多个 Fields 元素的 XML 解析为不同的类成员 这是 XML
  • 在气球内显示带有照片的多个地标的最佳做法是什么?

    我有一个项目如下 从手机上拍摄几张照片 将照片保存在网络系统中 然后将照片显示在其中的谷歌地球上 我读过很多文章 但它们都使用 fetchKml 我读过的一篇好文章是使用 php 但使用 fetchKml 我不知道是否可以使用 parseK
  • 如何正确配置Tomcat SSLHostConfig?

    我正在按照本教程在 tomcat 中启用 ssl https medium com raupach how to install lets encrypt with tomcat 3db8a469e3d2 https medium com
  • Java:检查给定日期是否在当前月份内

    我需要检查给定的日期是否在当前月份 我编写了以下代码 但 IDE 提醒我getMonth https docs oracle com javase 7 docs api java util Date html getMonth and ge
  • 无法从后台服务通过 WiFi 访问互联网

    我将直接介绍我发现的一些事实 数据 如果您遇到 解决了类似的问题 请帮助我 我每 5 分钟向服务器发送一次数据 除非用户在服务器的帮助下手动将其关闭 wakeful broadcast receiver通过一个intent service
  • 我们可以在三元运算符(Java)中使用命令吗?

    这是一个工作代码 String a first String b second String object System out println object null a b 但它不是 String a first String b se
  • Java 的 QP 求解器 [关闭]

    就目前情况而言 这个问题不太适合我们的问答形式 我们希望答案得到事实 参考资料或专业知识的支持 但这个问题可能会引发辩论 争论 民意调查或扩展讨论 如果您觉得这个问题可以改进并可能重新开放 访问帮助中心 help reopen questi
  • grails 上的同步块在 Windows 上有效,但在 Linux 上无效

    我有一个 grails 应用程序 它依赖于服务中的同步块 当我在 Windows 上运行它时 同步按预期工作 但当我在 ams linux 上运行时 会出现 StaleObjectStateException 该问题在以下示例中重现 cla
  • 为什么在尝试使用 Java 连接到 RDS PostgreSQL 数据库时会收到 SocketTimeoutException?

    我有一个 Spring 应用程序 我试图在 AWS 上托管 几天来我一直在努力配置 我有一个 EC2 实例 并且能够通过 SSH 连接到它 我还在 AWS 中设置了 Postgres RDS 数据库 但我无法使用 IDE 中的代码连接到它
  • java中的比较器链

    正在阅读Oracle 关于接口的 Java 教程 https docs oracle com javase tutorial java IandI createinterface html其中给出了一个例子Card 打牌 我试图理解接口中的
  • JDK 7 的快速调试/调试构建

    我正在寻找 JDK 的调试 或者我猜他们称之为快速调试构建 以启用在运行时生成的打印程序集以及查找性能问题时所需的其他诊断 就目前情况而言 我似乎找不到可以直接使用的 现成的 快速调试构建二进制包 有人可以帮我提供下载链接 或者至少提供有关
  • Java的hashCode可以为不同的字符串产生相同的值吗?

    使用java的哈希码函数是否可以为不同的字符串提供相同的哈希码 或者如果可能的话 其可能性的 是多少 Java 哈希码是 32 位 它散列的可能字符串的数量是无限的 所以是的 会发生冲突 百分比是没有意义的 项目 字符串 的数量是无限的 而
  • 监控 Java 应用程序上的锁争用

    我正在尝试创建一个小基准 在 Groovy 中 以显示几个同步方法上的高线程争用 当监控自愿上下文切换时 应该会出现高争用 在 Linux 中 这可以通过 pidstat 来实现 程序如下 class Res private int n s

随机推荐

  • Hive操作命令上手手册

    内容来自于 大数据Hive离线计算开发实战 Hive原理 Hive是一个基于Hadoop的数据仓库和分析系统 用于管理和查询大型数据集 以下是Hive的原理 数据仓库 Hive将结构化的数据文件映射成一张表 并提供类SQL查询功能 用户可以
  • InfluxDB学习笔记

    本博客是我在学习InfluxDB的时候 记录的笔记 大家可以看看参考学些 简介 简述 InfluxDB是一个由InfluxData开发的开源时序型数据 它由Go写成 着力于高性能查询与存储时序型数据 InfluxDB被广泛应用于存储系统的监
  • 高翔博士Faster-LIO论文和算法解析

    说明 题目 Faster LIO 快速激光IMU里程计 参考链接 Faster LIO 快速激光IMU里程计 iVox Faster Lio 智行者高博团队开源的增量式稀疏体素结构 Faster Lio是高翔博士在Fast系列的新作 对标基
  • 在JavaScript面向对象编程中使用继承

    面向对象编程是一种重要的编程范式 它通过将数据和操作封装在对象中 实现了代码的模块化和复用 在JavaScript中 我们可以使用继承来实现对象之间的关系 从而使代码更加灵活和可扩展 那么 让我们深入探讨在JavaScript中如何使用继承
  • 安达发APS|PDM产品数据管理可以帮助企业实现的价值

    在实际运用中 APS系统的PDM产品数据管理功能可以帮助企业实现以下价值 1 提高产品设计和制造的效率 通过对产品结构和文档的统一管理 可以实现对产品信息的快速查询和检索 提高产品设计和制造的效率 2 保证产品数据的准确性和一致性 通过对版
  • excel 按照姓名日期年份分组求和

    excel 需要按照 姓名 日期中年份分组求和 目前想到 sumifs函数 大概需求如下 表格数据大概如下 A B C 姓名 日期 金额 a 2022 2 1 542 a 2023 4 5 154 b
  • 基于多目标粒子群算法的三个目标的支配解求解,基于多目标粒子群的帕累托前沿求解,基于多目标粒子群的三目标求解

    目录 摘要 测试函数shubert 粒子群算法的原理 粒子群算法的主要参数 粒子群算法原理 基于多目标粒子群算法的支配解求解 基于多目标粒子群的帕累托前沿求解 基于多目标粒子群的三目标求解 代码 结果分析 展望 代码下载 基于多目标粒子群算
  • 线上企业展厅:企业发展新利器,轻松实现线上企业展示

    引言 在当今数字化时代 线上企业展厅已经成为企业展示品牌形象 推广产品的重要手段 它不仅可以帮助企业拓宽市场 提升品牌知名度 还能为企业带来更多的商业机会 那么线上企业展厅有什么优势 如何搭建成功的线上企业展厅 一 线上企业展厅的优势 1
  • 题解 | #链表中的节点每k个一组翻转#C++暴力遍历解法

    求大家投下我们腾讯吧 发的一堆sp被鸽了 大数据面试题 Hive 华为14A还是保研华中科技大学 求大家投下我们腾讯吧 发的一堆sp被鸽了 终极 2024校招八股文 MySQL索引 第一篇 招芯片验证实习生 碰到渣导悲哀3年 给秋招画个句号
  • Sage运行pwntools库脚本异常解决:OSError: Int or String expected

    需要和Oracle交互的密码学脚本一般都需要借助pwn库的帮助 今天切换了python版本后 出现了一个异常 OSError Int or String expected 详细异常见文章 查阅一下源码后简单的解决了这个问题 在此分享一下 文
  • 解锁数据之门Roxlabs全球住宅IP赋能海外爬虫与学术研究

    11 20云账房测试一面凉经 华为开奖啦 关于邮储的一些情况 维信金科一面 二面 hr面 我的导师太好了 上海维信金科 技术面试一 Java后端开发岗记录贴 维信金科正式批面经 软件技术领域就业大纲 1 公司分类 你裁掉这个应届生用了多长时
  • TypeScript 和 jsdom 库创建爬虫程序示例

    TypeScript 简介 TypeScript 是一种由微软开发的自由和开源的编程语言 它是 JavaScript 的一个超集 可以编译生成纯 JavaScript 代码 TypeScript 增加了可选的静态类型和针对对象的编程功能 使
  • 在职状态下继续学习的心得体会

    本来平时记录的都是一些技术点的学习和使用 今天打算记录一下学习方法 当然不一定适合所有人 因人而异 仅供参考 学习这件事 对于IT行业来说 真的是活到老学到老 技术的更新迭代速度非常快 而且总是有那么一些公司特别的卷 没办法 改变不了外因
  • 模型训练 -- 数据集的获取(如何筛选想要数据)

    目录 一 前言 二 数据平台 三 数据处理 1 MaixHub平台使用 2 ZIP格式整理与上传平台 3 数据处理 使用平台删除标注 四 参考 一 前言 对于图像数据集的获取 一直是一个麻烦点 自己不想去标注数据 太繁琐了 所以 我分享出一
  • 在线智能抠图软件有哪些?证件照背景颜色更换不求人

    2024年上半年全国计算机等级考试 NCRE 报名开始啦 不出意外 这次报名仍然是需要提交证件照 具体要求如下 本人近期正面免冠 白色背景 彩色证件照 不得使用生活照 美颜照 最小像素高宽为192x144 最小成像区高宽为48mmx33mm
  • 大模型笔记【2】 LLM in Flash

    Apple最近发表了一篇文章 可以在iphone MAC 上运行大模型 LLM in a flash Efficient Large Language Model Inference with Limited Memory 主要解决的问题是
  • ros2 基础学习 15- URDF:机器人建模方法

    URDF 机器人建模方法 ROS是机器人操作系统 当然要给机器人使用啦 不过在使用之前 还得让ROS认识下我们使用的机器人 如何把一个机器人介绍给ROS呢 为此 ROS专门提供了一种机器人建模方法 URDF Unified Robot De
  • 解锁数据之门Roxlabs全球住宅IP赋能海外爬虫与学术研究

    11 20云账房测试一面凉经 华为开奖啦 关于邮储的一些情况 维信金科一面 二面 hr面 我的导师太好了 上海维信金科 技术面试一 Java后端开发岗记录贴 维信金科正式批面经 软件技术领域就业大纲 1 公司分类 你裁掉这个应届生用了多长时
  • MySQL 8.0中新增的功能(五)

    改进哈希连接性能 MySQL 8 0 23重新实现了用于哈希连接的哈希表 从而改进了哈希连接的性能 这项工作包括修复了一个问题 Bug 31516149 Bug 99933 在这个问题中 用于连接缓冲区 join buffer size 的
  • 【设计模式之美】 SOLID 原则之五:依赖反转原则:将代码执行流程交给框架

    文章目录 一 控制反转 IOC 二 依赖注入 DI 三 依赖注入框架 DI Framework 四 依赖反转原则 DIP 一 控制反转 IOC 通过一个例子来看一下 什么是控制反转 public class UserServiceTest