Spring控制反转和依赖注入的好处

2023-11-18

要了解控制反转( Inversion of Control ), 我觉得有必要先了解软件设计的一个重要思想:依赖倒置原则(Dependency Inversion Principle )

什么是依赖倒置原则? 假设我们设计一辆汽车:先设计轮子,然后根据轮子大小设计底盘,接着根据底盘设计车身,最后根据车身设计好整个汽车。这里就出现了一个“依赖”关系:汽车依赖车身,车身依赖底盘,底盘依赖轮子。
在这里插入图片描述
这样的设计看起来没问题,但是可维护性却很低。假设设计完工之后,上司却突然说根据市场需求的变动,要我们把车子的轮子设计都改大一码。这下我们就蛋疼了:因为我们是根据轮子的尺寸设计的底盘,轮子的尺寸一改,底盘的设计就得修改;同样因为我们是根据底盘设计的车身,那么车身也得改,同理汽车设计也得改——整个设计几乎都得改!
我们现在换一种思路。我们先设计汽车的大概样子,然后根据汽车的样子来设计车身,根据车身来设计底盘,最后根据底盘来设计轮子。这时候,依赖关系就倒置过来了:轮子依赖底盘, 底盘依赖车身, 车身依赖汽车。
在这里插入图片描述
这时候,上司再说要改动轮子的设计,我们就只需要改动轮子的设计,而不需要动底盘,车身,汽车的设计了。
这就是依赖倒置原则——把原本的高层建筑依赖底层建筑“倒置”过来,变成底层建筑依赖高层建筑。高层建筑决定需要什么,底层去实现这样的需求,但是高层并不用管底层是怎么实现的。这样就不会出现前面的“牵一发动全身”的情况。

控制反转(Inversion of Control) 就是依赖倒置原则的一种代码设计的思路。具体采用的方法就是所谓的依赖注入(Dependency Injection)。其实这些概念初次接触都会感到云里雾里的。说穿了,这几种概念的关系大概如下:
在这里插入图片描述
为了理解这几个概念,我们还是用上面汽车的例子。只不过这次换成代码。我们先定义四个Class,车,车身,底盘,轮胎。然后初始化这辆车,最后跑这辆车。代码结构如下:
在这里插入图片描述
这样,就相当于上面第一个例子,上层建筑依赖下层建筑——每一个类的构造函数都直接调用了底层代码的构造函数。假设我们需要改动一下轮胎(Tire)类,把它的尺寸变成动态的,而不是一直都是30。我们需要这样改:
在这里插入图片描述
由于我们修改了轮胎的定义,为了让整个程序正常运行,我们需要做以下改动:
在这里插入图片描述
由此我们可以看到,仅仅是为了修改轮胎的构造函数,这种设计却需要修改整个上层所有类的构造函数! 在软件工程中,这样的设计几乎是不可维护的——在实际工程项目中,有的类可能会是几千个类的底层,如果每次修改这个类,我们都要修改所有以它作为依赖的类,那软件的维护成本就太高了。
所以我们需要进行控制反转(IoC),及上层控制下层,而不是下层控制着上层。我们用依赖注入(Dependency Injection)这种方式来实现控制反转。所谓依赖注入,就是把底层类作为参数传入上层类,实现上层类对下层类的“控制”。 这里我们用构造方法传递的依赖注入方式重新写车类的定义:
在这里插入图片描述
这里我们再把轮胎尺寸变成动态的,同样为了让整个系统顺利运行,我们需要做如下修改:
在这里插入图片描述
看到没?这里我只需要修改轮胎类就行了,不用修改其他任何上层类。这显然是更容易维护的代码。不仅如此,在实际的工程中,这种设计模式还有利于不同组的协同合作和单元测试:比如开发这四个类的分别是四个不同的组,那么只要定义好了接口,四个不同的组可以同时进行开发而不相互受限制;而对于单元测试,如果我们要写Car类的单元测试,就只需要Mock一下Framework类传入Car就行了,而不用把Framework, Bottom, Tire全部new一遍再来构造Car。
这里我们是采用的构造函数传入的方式进行的依赖注入。其实还有另外两种方法:Setter传递和接口传递。这里就不多讲了,核心思路都是一样的,都是为了实现控制反转
在这里插入图片描述
看到这里你应该能理解什么控制反转和依赖注入了。那什么是控制反转容器(IoC Container)呢?其实上面的例子中,对车类进行初始化的那段代码发生的地方,就是控制反转容器。
在这里插入图片描述
显然你也应该观察到了,因为采用了依赖注入,在初始化的过程中就不可避免的会写大量的new。这里IoC容器就解决了这个问题。这个容器可以自动对你的代码进行初始化,你只需要维护一个Configuration(可以是xml可以是一段代码),而不用每次初始化一辆车都要亲手去写那一大段初始化的代码。这是引入IoC Container的第一个好处。
IoC Container的第二个好处是:我们在创建实例的时候不需要了解其中的细节。在上面的例子中,我们自己手动创建一个车instance时候,是从底层往上层new的:
在这里插入图片描述
这个过程中,我们需要了解整个Car/Framework/Bottom/Tire类构造函数是怎么定义的,才能一步一步new/注入。
而IoC Container在进行这个工作的时候是反过来的,它先从最上层开始往下找依赖关系,到达最底层之后再往上一步一步new(有点像深度优先遍历):
在这里插入图片描述
这里IoC Container可以直接隐藏具体的创建实例的细节,在我们来看它就像一个工厂:
在这里插入图片描述
我们就像是工厂的客户。我们只需要向工厂请求一个Car实例,然后它就给我们按照Config创建了一个Car实例。我们完全不用管这个Car实例是怎么一步一步被创建出来。
实际项目中,有的Service Class可能是十年前写的,有几百个类作为它的底层。假设我们新写的一个API需要实例化这个Service,我们总不可能回头去搞清楚这几百个类的构造函数吧?IoC Container的这个特性就很完美的解决了这类问题——因为这个架构要求你在写class的时候需要写相应的Config文件,所以你要初始化很久以前的Service类的时候,前人都已经写好了Config文件,你直接在需要用的地方注入这个Service就可以了。这大大增加了项目的可维护性且降低了开发难度。

作者:Mingqi
链接:https://www.zhihu.com/question/23277575/answer/169698662
来源:知乎

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

Spring控制反转和依赖注入的好处 的相关文章

随机推荐

  • 等价类

    动态测试方法是指通过运行被测程序 检查运行结果与预期结果的差异 并分析运行效率 正确性和健壮性等性能 这种方法由三部分组成 构造测试用例 执行程序 分析程序的输出结果 静态方法是指不运行被测程序本身 仅通过分析或检查源程序的语法 结构 过程
  • 无线通信原理之OFDM技术

    补充一个完整的OFDM系统结构图 包括收发天线 目录 1 OFDM的基本原理 2 OFDM系统模型 3 循环前缀和导频 4 OFDM系统参数 1 OFDM的基本原理 OFDM即正交频分复用 Orthogonal Frequency Divi
  • React-错误边界与组件通信方式概述

    错误边界 错误边界 Error boundary 用来捕获后代组件错误 渲染出备用页面 注意 只在生产环境 项目上线 起效 特点 只能捕获后代组件生命周期产生的错误 不能捕获自己组件产生的错误和其他组件在合成事件 定时器中产生的错误 简单理
  • DevOps是什么

    DevOps 英文Development和Operations的组合 是一组过程 方法与系统的统称 用于促进开发 应用程序 软件工程 技术运营和质量保障 QA 部门之间的沟通 协作与整合 它的出现是由于软件行业日益清晰地认识到 为了按时交付
  • JavaBean SpringBean是对象还是类

    JavaBean SpringBean是对象还是类 什么是JavaBean 什么是SpringBean 首先先说结论 Bean可以理解为对象 这几天在学习Spring源码的时候 观察到底层反复的对Bean的操作 于是就去网上搜索Bean到底
  • JAVA小程序微信支付

    微信支付有专门的文档 https pay weixin qq com wiki doc api wxa wxa api php chapter 9 1 当时找的时候都是前台如何 后来才发现后台需要做的就是统一下单 一 先到微信下载两个证书
  • java实现冒泡排序,直接插入排序,选择排序,希尔排序,以及求出它们的时间复杂度O(n)

    package com yg sort author GeQiLin date 2020 2 25 16 53 import java util Arrays public class Sort1 private static int ma
  • ModuleNotFoundError: No module named 'tqdm'

    bogon faceswap master macname pip3 install tqdm Collecting tqdm Downloading https files pythonhosted org packages 9f 3d
  • 软件版本(release、stable、lastest)的区别

    snapshot 快照 也即开发版 我们创建maven项目时 编辑器会自动给我们填入 1 0 SNAPSHOT版本 也就是1 0开发版 这个版本不能使用 因为该版本处于开发的过程 所以运行时会不时地更新 导致功能变化 正式环境中不得使用sn
  • layer.js open 隐藏滚动条

    img echart trand click function var host this data host var role this data role console log host host console log window
  • 华为防火墙NAT

    目录 NAT分类 黑洞路由 Server map 表 NAT分类 在内外网的边界 流量有出 入两个方向 所以NAT技术包含源地址转换和目的地址转换 一般情况下 源地址转换主要解决内部局域网计算机访问internet的场景 而目标地址转换主要
  • Qt/C++项目架构经验总结

    一 通用规则 除了极小的微型demo级别项目外 其余项目建议用pri分门别类不同文件夹存放代码文件 方便统一管理和查找 同类型功能的类建议统一放在一起 如果该目录下代码文件数量过多 也建议拆分多个目录存放 比如就3 5个界面的项目 统一搞个
  • Android 编码规范

    文章目录 背景 制定规范 一 开发环境 二 命名规范 1 资源命名 2 包命名 3 方法命名 4 类命名 5 变量 6 常量 二 编码规范 三 注释规范 1 类注释 2 方法注释 2 变量注释 3 区块注释 四 代码提交规范 五 其他须遵循
  • 快乐的强化学习4——Policy Gradients及其实现方法

    快乐的强化学习4 Policy Gradients及其实现方法 学习前言 简介 举例应用 神经网络的构建 动作的选择 神经网络的学习 具体实现代码 学习前言 刚刚从大学毕业 近来闲来无事 开始了机器学习的旅程 深度学习是机器学习的重要一环
  • [论文解读] 多机器人系统动态任务分配综述

    https www emerald com insight content doi 10 1108 IR 04 2020 0073 full html 多机器人 多智能体 动态环境 任务分配 决策 动态任务调度策略 该文章主要是想对目前st
  • jmeter+maven

    1 创建maven Project jmeter test 2 文件 1 src test目录下创建jmeter文件 2 src test jmeter目录下创建xx项目名文件 3 xxx jmx 脚本文件及依赖文件 放在src test
  • ajax post微信接口,微信小程序的ajax数据请求wx.request介绍

    微信小程序的ajax数据请求wx request介绍 微信小程序的ajax数据请求 很多同学找不到api在哪个位置 这里单独把小程序的ajax请求给列出来 微信小程序的请求就是wx request这个api wx request 一些对象参
  • 语音增强技术

    我的书 淘宝购买链接 当当购买链接 京东购买链接 在人机交互的场景中 麦克风采集到的人声不可避免的混杂外界噪声 可以通过信号处理的方法在处理前将其去除 以获得 纯净 的语音信号 这一过程称为降噪或者语音增强 从麦克风数量上分为单麦克降噪 多
  • C++程序员视角下的Rust语言

    自上世纪80年代初问世以来 C 就是一门非常重要的系统级编程语言 到目前为止 仍然在很多注重性能 实时性 偏硬件等领域发挥着重要的作用 C 和C一样 可以通过指针直接操作内存 这给了程序员的编程提供了相当大的自由度 但指针就是一把双刃剑 给
  • Spring控制反转和依赖注入的好处

    要了解控制反转 Inversion of Control 我觉得有必要先了解软件设计的一个重要思想 依赖倒置原则 Dependency Inversion Principle 什么是依赖倒置原则 假设我们设计一辆汽车 先设计轮子 然后根据轮