什么是依赖注入
几个关键要点:
- 依赖:对象之间不可避免会有依赖关系,例如:car->engine (汽车依赖发动机,举这个例子是为了搬砖方便)。
依赖存在的问题:
1)依赖不可避免。
2)代码的修改、维护不可避免(增减功能、重构)
3)依赖会导致代码修改时涉及的代码更多,更容易引入bug。 - 实现方式:
1)可以在car类中创建一个engine。
2)在car类中调用一个全局接口(比如单例)获得一个engine对象。
3)外部通过参数传递一个engine对象给car,例如car的构造函数或者setter方法。 - 依赖注入:上述第三种方式就是依赖注入,对象之间的依赖关系通过传参方式来实现。
1)简单说,把一个对象通过参数传递给另一个对象,就是依赖注入。
2)好处:只有一点,就是灵活(涵盖功能变更、重构、测试等场景)。上述3种实现方式,在发生变动时,可能存在的情况:1)要换engine,需要改car的代码。2)car依赖engine,以及系统提供一个获取引擎的接口(单例不便于测试,不同的测试用例之间需要独立)。3)只要是传入engine的子类对象,car代码不需要修改,也不会额外依赖其他接口。是最简单的实现方式。
灵活性体现在,无论是汽油发动机、柴油发动机还是电动机,car不用修改,好比实际中的一个车型有多种动力方案。
3)传参要搞这么复杂吗?主要是为了方便沟通,以及装逼。原理简单,但在工程中存在复杂性,代表一类问题。
代码示例
走2段代码:
代码一: car创建一个engine对象(上述第一种实现方式)
class Car {
private val engine = Engine()
fun start() {
engine.start()
}
}
fun main(args: Array) {
val car = Car()
car.start()
}
代码二:依赖注入,通过参数传入engine,上述第三种实现方式
class Car(private val engine: Engine) {
fun start() {
engine.start()
}
}
fun main(args: Array) {
val engine = Engine()
val car = Car(engine)
car.start()
}
复杂性和解决方案
实际项目的代码可能有上万行甚至更多,多人、多团队同时开发,以及人员变动、长期维护等因素,让依赖注入的实现变得比较复杂:
1)数量:大量对象之间的依赖,几十个、上百个,比如对汽车进行建模编程。
2)依赖关系复杂:逻辑和时序两方面,逻辑上存在不同的层次的对象依赖,子对象、孙对象);时序上,如何保证传入的对象已经创建。在复杂工程中,往往由不同的人员和团队开发多个模块,让相互之间的依赖关系更加复杂。
再深入一些,比如要管理对象的声明周期,为了安全或者回收资源,需要及时销毁对象,控制对象的使用范围。后期维护人员,不是最初的开发人员,如何能够正确的使用各个对象,避免引入错误逻辑或者冗余代码(原来代码看不懂,绕过去写新代码。这样做危害也很大。)
解决方案就是通过一些库辅助解决开发人员解决依赖的复杂性问题,比如:Dagger,Hilt等。
问题的另一面,开头提到的3种方式都是解决方案,实现的复杂性不同,分别看一下:
1)在car内创建一个engine,这种方式最直观,在car内部使用的时候,非常简单、可靠,不用担心engine被其他人动手脚,对象的创建、销毁、使用都在可见范围内。
2)调用单例接口获取一个engine,中实现是有car主动获取engine对象,单例接口对对象初始化有一定的保证,对象的销毁不受控制。如果单例接口是系统提供的接口,例如某个getService,可靠性也更有保障。
3)依赖注入的方式,最为灵活,对car来说要管的事情最少,也最不可控。传入的对象可能是初始化好的,也可能是一个null或者初始化一半的,使用过程中可能被修改或者销毁了。
本质:
1)用“依赖注入”带来的较小的复杂性和成本,解决实际工程中更大的复杂性和更高的成本。
2)复杂性是客观存在的,并且灵活性是必须的,实际中car是无法管控engine的变动,如果car强行管理engine对象会带来更大的维护成本。既然管不了,只能承担部分风险(使用过程中对象不完全可控)。
3)对于不同规模的项目,各种实现各有优劣。
知识扩展
结合设计模式六原则,体会设计模式的实际应用。
Single Responsibility Principle:单一职责原则
Open Closed Principle:开闭原则
Liskov Substitution Principle:里氏替换原则
Law of Demeter:迪米特法则
Interface Segregation Principle:接口隔离原则
Dependence Inversion Principle:依赖倒置原则
当我们在说依赖注入时在说什么?
1)开发经验,是否理解软件工程项目的复杂性,意识到问题才能解决问题或者应用解决方案。
2)编程语言、系统api、需求、架构模式,以及如何融合这些知识本身都是技术的组成部分。
3)上面2点是更深层次的,表面上和实战上就是问题原理、解决方案,代码库的原理和实际使用。