阿里云「飞天发布时刻」2024来啦!新产品、新特性、新能力、新方案,等你来探~ 了解详情
写点什么

一文说透依赖注入

  • 2021-03-26
  • 本文字数:3076 字

    阅读完需:约 10 分钟

一文说透依赖注入

控制反转在整洁架构中非常重要,而大部分现代框架的设计思想都是暗合《架构整洁之道》的,因此,在大部分现代框架中,都内置了依赖注入机制,以支持控制反转。

什么是依赖?

每个软件,都是由很多“组件”构成的。这里的“组件”是指广义的组件 —— 组成部件,它可能是函数,可能是类,可能是包,也可能是微服务。软件的架构,就是组件以及组件之间的关系。而这些组件之间的关系,就是(广义的)依赖关系。

依赖有多重要?

软件的维护工作,本质上都是由“变化”引起的,只要软件还活着,我们就无法对抗变化,只能顺应它。而组件之间的依赖关系决定了变化的传导范围。


一般来说,当被依赖的组件变化时,其依赖者也会随之变化。软件开发最怕的就是牵一发而动全身。所幸,并不是每次变化都必然会传导给它的依赖者们。


对于具体实现细节的修改,只要没有改变其外部契约(可简单理解为接口),其依赖者就不需要修改。对于更大规模的修改,比如更换计费策略,我们是不是就无法控制其传播了?也不见得。只要我们的设计能让两者的接口保持一致,就可以把变化控制在尽可能小的范围内。

整洁架构与 IoC

对于减少变更传播的方式,Bob 大叔在《架构整洁之道》中做过系统性的阐述,如下图:



在这张图中,我们要注意箭头的指向,箭头的指向就是依赖的方向。我们看到,所有的依赖都指向了用例(领域服务)并最终指向了业务实体。这样的架构很理想,因为业务实体的变化通常都比较少,即使变,通常也是必要的、无法回避的变化。反之,支持新的用户界面、换数据库、换外部系统等方面的变化都只会在很小的范围内传播。回顾一下你维护过的系统的各种变更,都属于哪一类呢?


这张图看起来很美,却和我们传统的编程方式截然不同。以展示器为例,在传统的编程方式下,我们需要在展示器中创建用户界面类的实例,并且设置用户界面的值以及接收来自用户界面的事件。这时候,我们就不得不让展示器“引用”用户界面的类,这样就产生了一个从展示器指向用户界面的依赖,破坏了架构的整洁性。


所以,我们要找到这样一种方式,让展示器能“控制”界面类。更泛化的说法是,让位于架构内层的组件,“控制”位于架构外层的组件,“控制”的方向与“依赖”的方向相反,所以,我们称其为“控制反转”(IoC)。

为什么要用依赖注入来管理依赖?

要想解除展示器对界面类的这种错误依赖,我们可以从界面类抽象出一个接口,展示器定义这样一个接口,然后让界面类实现它,这样展示器就不需要再知道界面类的存在了。唯一的问题是:接口本身是抽象的,所以它不能被直接实例化。如果让展示器负责实例化界面类,那么展示器又必须引用界面类了。好吧,一切似乎又回到了原点……不过,我们有另外的办法打破这种僵局。


引入第三方,通常是打破僵局的最佳方式之一。我们需要这样一个第三方:


  1. 它既知道如何将界面类实例化,又知道谁要控制这个实例所实现的接口。

  2. 它必须是个通用逻辑,也就是说它不应该关心界面类如何实例化的细节,它唯一要了解的信息是界面接口和界面类之间的“实现”关系,以及展示器与界面接口之间的“需要”关系。

  3. 它应该超然于整洁架构之外,作为整个架构的大管家,揽下所有“脏活儿”,让架构中的各个组件保持整洁。

  4. 展示器通常不用主动找这位大管家,因为大管家能看懂展示器的每一个眼神,展示器只要过“衣来伸手,饭来张口”的生活就可以了。当然,必要时展示器也可以主动跟大管家要这要那,不过,宅是一种幸福,费口舌的事情当然是越少越好。


这种“衣来伸手,饭来张口”的模式就叫做依赖注入,简称 DI。它是实现 IoC 最常见的方式。这个大管家,有个人尽皆知的名片,上面写着几个大字:Injector(注入器),当然也有人写得啰嗦一点:DI Container(注入容器)。


依赖注入实现原理简介

依赖注入的工作逻辑是这样的:

  1. 在应用启动时,会把界面类注册到注入器的“提供者映射表”(界面接口: 界面类)中,并且以界面接口作为自己的标记(Token)。也就是说把这个界面类注册为这个界面接口的提供者,当需要界面接口的实例时,可以由这个界面类来提供它。

  2. 在应用启动时,会把展示器类注册到注入器的“依赖树”(展示器 ---> 界面接口)中,表示展示器依赖一个界面接口的实例。如果展示器还依赖其它接口,那就还有更多类似的条目。而界面接口的提供者(界面类)还可能有其自身的依赖,最终形成一棵巨大的树。

  3. 当应用开始运行时,展示器会被实例化。注入器在实例化它的时候会发现展示器依赖一个界面接口的实例,于是注入器会尝试在其“实例映射表”(界面接口:界面接口实例)中根据这个界面接口查找相应的实例。如果找不到,就会去“提供者映射表”中查找这个界面接口的提供者,这里找到的是界面类。于是,注入器将这个界面类实例化,并将其记入“实例映射表”中,下次就不必再重新初始化它了。如果界面类还有自己的依赖,这个过程就会递归进行。


所以,注入器是这个依赖注入体系中的核心角色,它通过三个内部数据结构:提供者映射表、依赖树、实例映射表来完成整个过程。


但是,往“提供者映射表”中注册的过程该由谁来做呢?如果由界面类或展示器类自己做,那么它们将不得不“引用”注入器,而这不符合注入器的“人设”,理想的注入器应该是默默无闻的,最好让谁都不需要知道它。


那就只能让注入器自行完成这项工作了。


问题在于,注入器要怎么知道哪些类是可注入的呢?所以,我们要让这些可注入的类在实例化之前就具有某些特征,比如静态属性、静态方法,或者注解(Annotation)。


注解是最常见的方式,它像注释一样不被当做代码来执行,而是专门供别人阅读。但注释的读者完全是人类,而注解的主要读者除了人类之外还有框架或预编译器。


启动的时候,注入器会搜集具有指定特征的类,并将其注册到提供者映射表中,并且查找这些类的构造函数或具有某些特征的属性等,分析出其依赖关系,构建出“依赖树”。

更多的提供者

在前面的例子中,为了便于理解,我一直用类作为依赖提供者。但实际上,依赖提供者可以有很多形态:常量、变量、函数或者类都是可以的。


我们对依赖提供者唯一的期望是:它能给我们提供一个与特定标记对应的实例。至于这个实例是 new 出来的还是调用出来的,我们根本不需要关心。


通过这种面向目标而非面向实现的抽象化设计,我们为应对未来的变化留出了大量的灵活性,却不用付出多少代价。

依赖注入的实际应用

控制反转在整洁架构中非常重要,而大部分现代框架的设计思想都是暗合《架构整洁之道》的,因此,在大部分现代框架中,都内置了依赖注入机制,以支持控制反转。


最经典、最著名的当属 Spring Framework,可以说依赖注入无处不在。在 Java 后端开发中它几乎一统江湖就是其成功的见证。


在 Android Framework 等移动端框架中,依赖注入也是最常见的框架功能之一。


在前端领域,Angular 从 2009 年的 AngularJS 0.x 开始就支持了依赖注入。到了 Angular 2+ 的时代,借助 TypeScript 的语言特性,它甚至做得比 Spring Framework 还要全面、深入。

通过依赖注入改善设计

在面向对象编程的经典著作《设计模式》中,策略模式是使用最广泛的模式之一。但自行实现策略模式很繁琐,也容易出错。而依赖注入是策略模式的最佳实现方式之一。如果我们在设计中使用了策略模式,那么所有繁琐的工作都可以交给依赖注入框架来完成。我们抽象好策略接口,等将来需要换个策略时,只要修改下配置就可以了,而消费方的代码一点都不需要改。


深入理解了依赖注入技术,就可以将其灵活运用在你的各项实际工作中,设计出优美而灵活的应用架构。未来的你,以及接手你工作成果的人,会从你的高质量设计中受益匪浅。


本文转载自:ThoughtWorks 洞见(ID:TW-Insights)

原文链接:一文说透依赖注入

2021-03-26 08:004874

评论

发布
暂无评论
发现更多内容

构建数字合作格局 赋能政企行业通信——首届WECC 2021即将召开

融云 RongCloud

音视频 IT, 通信 通信云 会议

写给初学者,一文搞懂大数据学习、岗位、面试及简历

五分钟学大数据

大数据

三面阿里,有惊无险成功拿到offer定级P7,只能说是真的难

Java 编程 java架构

Elasticsearch 分片速度、进度及故障排查(qbit)

qbit

elasticsearch shard

WhatsApp 如何启用端到端加密备份数据

CatTalk

facebook 安全 端到端加密

面试官提问:如何通过sql方式将数据库表行转列?

Java 数据库 sql 面试 后端

遭 GitHub 连夜封杀下架?被泄露的阿里内部 Java 面试手册到底有多强?

收到请回复

Java 面试 阿里 大厂Offer

云栖大会|盛宴之下,共赴一场视频云的进化论

阿里云视频云

阿里云 音视频 WebRTC 视频云 云栖大会

Elasticsearch 快照相关(qbit)

qbit

分布式缓存技术

黄敏

Python 的 sum():Pythonic 的求和方法

华为云开发者联盟

Python 列表 元组 Pythonic 求和

Apache ShardingSphere 在京东白条场景的落地之旅

SphereEx

开源 数据架构 架构设计 ShardingSphere SphereEx

211本+985硕+计算机专业投面百度,坐等一周迎来三面,已拿offer

Java 学习 程序员 架构 大厂面试

详解物联网Modbus通讯协议

华为云开发者联盟

物联网 通信 Modbus 通讯协议 TCP通信

百亿级系统架构首公开!阿里这份300多页的设计实录你还没有吗?

Java 程序员 架构 面试 后端

解读鸿蒙轻内核的监控器:异常钩子函数

华为云开发者联盟

鸿蒙 钩子函数 任务栈 OpenHarmony 异常钩子函数

主数据与主数据管理(数据治理)

KoLee

数据治理 数字化 主数据管理 主数据

涨薪60%,从美团干到阿里p7,这份Github上的面试笔记把所有Java知识都写出来了

Java 程序员 架构 面试 后端

在Vue中使用JSX,很easy的

华为云开发者联盟

JavaScript Vue Vue3 JSX 渲染函数

DataOps(数据运维)指南 - 数据管理的新时代

码语者

DataOps

Android技术分享| 【自习室】自定义View代替通知动画(1)

anyRTC开发者

android 音视频 WebRTC 在线教育 移动开发

太绝了吧! 终于有人能把TCP/IP 协议讲的明明白白了

程序员 架构 面试 后端 java

第 17 章 -《Linux 一学就会》- Linux计划任务与日志的管理

学神来啦

Linux 运维 linux学习 linux云计算 linux基础

量化模拟线上流量实践

FunTester

性能测试 接口测试 测试框架 FunTester 线上流量

安全稳定便捷! 融云赋能“轻云会议”满足政府在线会议需求

融云 RongCloud

云计算 音视频 通信 会议 视频云

Alibaba最新神作!耗时182天肝出来的1015页分布式全栈手册太香了

编程 程序员 IT 计算机 java

元宇宙NFT区块链游戏系统开发

19. 删除链表的倒数第N个数(链表)

黄敏

J2PaaS低代码平台的开源,将进一步助力企业数字化

J2PaaS低代码平台

低代码 低代码开发 低代码开发平台

2021Flexera云报告:企业积极拥抱多云,但云上成本仍然居高不下

行云管家

区块链 云计算 企业上云 上云

Python代码阅读(第37篇):获取两个列表中相同的元素

Felix

Python 编程 Code Programing 阅读代码

一文说透依赖注入_架构_张凯峰_InfoQ精选文章