深入浅出依赖注入及其在抖音直播中的应用

本文探讨了抖音直播如何利用依赖注入实现模块间低耦合,遵循DIP和IoC原则,通过控制反转降低组件间复杂性,提升代码灵活性和可维护性。同时分析了依赖注入的优缺点及其在大型项目中的应用策略。

动手点关注 干货不迷路 👆

前言

近三年,抖音直播业务实现了爆发式增长,直播间的功能也增添了许多的可玩性。为了高效满足业务快速迭代的诉求,抖音直播非常深度的使用了依赖注入架构。

在软件工程中,依赖注入(dependency injection)的意思为:给予调用方它所需要的事物。

“依赖”是指可被方法调用的事物。依赖注入形式下,调用方不再直接使用“依赖”,取而代之是“注入” 。

“注入”是指将“依赖”传递给调用方的过程。在“注入”之后,调用方才会调用该“依赖”。

传递依赖给调用方,而不是让让调用方直接获得依赖,这个是该设计的根本需求。该设计的目的是为了分离调用方和依赖方,从而实现代码的高内聚低耦合,提高可读性以及重用性。

本文试图从原理入手,讲清楚什么是依赖,什么是反转,依赖反转与控制反转的关系又是什么?一个依赖注入框架应该具备哪些能力?抖音直播又是如何通过依赖注入优雅的实现模块间的解耦?通过对依赖注入架构优缺点的分析,能对其能有更全面的了解,为后续的架构设计工作带来更多的灵感。

什么是依赖

对象间依赖

面向对象设计及编程的基本思想,简单来说就是把复杂系统分解成相互合作的对象,这些对象类通过封装以后,内部实现对外部是透明的,从而降低了解决问题的复杂度,而且服务可以灵活地被重用和扩展。而面向对象设计带来的最直接的问题,就是对象间的依赖。

我们举一个开发中最常见的例子:

在 A 类里用到 B 类的实例化构造,就可以说 A 依赖于 B。软件系统在没有引入 IOC 容器之前,对象 A 依赖于对象 B,那么对象 A 在初始化或者运行到某一点的时候,自己必须主动去创建对象 B 或者使用已经创建的对象 B。无论是创建还是使用对象 B,控制权都在 A 自己手上。

这个直接依赖会导致什么问题?

  • 过渡暴露细节

    • A 只关心 B 提供的接口服务,并不关心 B 的内部实现细节,A 因为依赖而引入 B 类,间接的关心了 B 的实现细节

  • 对象间强耦合

    • B 发生任何变化都会影响到 A,开发 A 和开发 B 的人可能不是一个人,B 把一个 A 需要用到的方法参数改了,B 的修改能编译通过,能继续用,但是 A 就跑不起来了

  • 扩展性差

    • A 是服务使用者,B 是提供一个具体服务的,假如 C 也能提供类似服务,但是 A 已经严重依赖于 B 了,想换成 C 非常之困难

学过面向对象的同学马上会知道可以使用接口来解决上面几个问题。如果早期实现类 B 的时候就定义了一个接口,B 和 C 都实现这个接口里的方法,这样从 B 切换到 C 是不是就只需很小的改动就可以完成。

A 对 B 或 C 的依赖变成对抽象接口的依赖了,上面说的几个问题都解决了。但是目前还是得实例化 B 或者 C,因为 new 只能 new 对象,不能 new 一个接口,还不能说 A 彻底只依赖于接口了。从 B 切换到 C 还是需要修改代码,能做到更少的依赖吗?能做到 A 在运行的时候想切换 B 就 B,想切换 C 就 C,不用改任何代码甚至还能支持以后切换成 D 吗?

通过反射可以简单实现上面的诉求。例如常用的接口NSClassFromString,通过字符串可以转换成同名的类。通过读取本地的配置文件,或者服务端下发的数据,通过 OC 的提供的反射接口得到对应的类,就可以做到运行时动态控制依赖对象的引入。

软件系统的依赖

让我们把视角放到更大的软件系统中,这种依赖问题会更加突出。

在面向对象设计的软件系统中,它的底层通常都是由 N 个对象构成的,各个对象或模块之间通过相互合作,最终实现系统地业务逻辑。

9570daed7fa65630eca5b35c327583b9.png

如果我们打开机械式手表的后盖,就会看到与上面类似的情形,各个齿轮分别带动时针、分针和秒针顺时针旋转,从而在表盘上产生正确的时间。

上图描述的就是这样的一个齿轮组,它拥有多个独立的齿轮,这些齿轮相互啮合在一起,协同工作,共同完成某项任务。我们可以看到,在这样的齿轮组中,如果有一个齿轮出了问题,就可能会影响到整个齿轮组的正常运转。

齿轮组中齿轮之间的啮合关系,与软件系统中对象之间的耦合关系非常相似。

对象之间的耦合关系是无法避免的,也是必要的,这是协同工作的基础。功能越复杂的应用,对象之间的依赖关系一般也越复杂,经常会出现对象之间的多重依赖性关系,因此,架构师对于系统的分析和设计,将面临更大的挑战。对象之间耦合度过高的系统,必然会出现牵一发而动全身的情形。

bce4e2d73b0b6e638467ba7a58ec17a3.png

耦合关系不仅会出现在对象与对象之间,也会出现在软件系统的各模块之间。如何降低系统之间、模块之间和对象之间的耦合度,是软件工程永远追求的目标之一。

控制反转

为了解决对象之间的耦合度过高的问题,软件专家 Michael Mattson 1996 年提出了 IOC 理论,用来实现对象之间的“解耦”,目前这个理论已经被成功地应用到实践当中。

1996 年,Michael Mattson 在一篇有关探讨面向对象框架的文章中,首先提出了 IOC (Inversion of Control / 控制反转)这个概念。

IOC 理论提出的观点大体为:借助于“第三方”实现具有依赖关系的对象之间的解耦。如下图:

0385892f968cefa8f65cc75fadf2b51e.png

由于引进了中间位置的“第三方”,也就是 IOC 容器,使得 A、B、C、D 这 4 个对象没有了耦合关系,齿轮之间的传动全部依靠“第三方”了,全部对象的控制权全部上缴给“第三方”IOC 容器,所以,IOC 容器成了整个系统的关键核心,它起到了一种类似“粘合剂”的作用,把系统中的所有对象粘合在一起发挥作用,如果没有这个“粘合剂”,对象与对象之间会彼此失去联系,这就是有人把 IOC 容器比喻成“粘合剂”的由来。

我们再来做个试验:把上图中间的 IOC 容器拿掉,然后再来看看这套系统:

510d3182001424a3764de4848d2e768c.png

我们现在看到的画面,就是我们要实现整个系统所需要完成的全部内容。这时候,A、B、C、D 这 4 个对象之间已经没有了耦合关系,彼此毫无联系,这样的话,当你在实现 A 的时候,根本无须再去考虑 B、C 和 D 了,对象之间的依赖关系已经降低到了最低程度。所以,如果真能实现 IOC 容器,对于系统开发而言,这将是一件多么美好的事情,参与开发的每一成员只要实现自己的类就可以了,跟别人没有任何关系!

软件系统在引入 IOC 容器之后,对象间依赖的情况就完全改变了,由于 IOC 容器的加入,对象 A 与对象 B 之间失去了直接联系,所以,当对象 A 运行到需要对象 B 的时候,IOC 容器会主动创建一个对象 B 注入到对象 A 需要的地方

通过前后的对比,我们不难看出来:对象 A 获得依赖对象 B 的过程,由主动行为变为了被动行为,控制权颠倒过来了,这就是“控制反转”这个名称的由来。

依赖反转与控制反转

没有反转

当我们考虑如何去解决一个高层次的问题的时候,我们会将其拆解成一系列更细节的较低层次的问题,再将每个较低层次的问题拆解为一系列更低层次的问题,这就是业务逻辑(控制流)的走向,是「自顶向下」的设计。

如果按照这样的拆解问题的思路去组织我们的代码,那么代码架构的走向也就和业务逻辑的走向一致了,也就是没有反转的情况。

没有依赖反转的情况下,系统行为决定了控制流,控制流决定了代码的依赖关系

以抖音直播为例:直播有房间的概念,房间内包含多个功能组件。对应的,代码里有一个房间服务的控制器类(如RoomController),一个组件管理的类(ComponentLoader),以及若干组件类(如红包组件RedEnvelopeComponent 、礼物组件 GiftComponent)。

进入直播房间时,先创建房间控制器,控制器会创建组件管理类,接着组件管理类会初始化房间内所有组件。这里的描述就是业务逻辑(控制流)的方向。

如果按照没有反转的情况,控制流和代码依赖的示意图如下:

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值