依赖注入(DI)是一门广泛应用于编程的技术,遵循DI架构可以为良好的应用架构奠定基础。DI的优势:
1.代码复用 2.易于重构 3.易于测试
一、什么是依赖注入(DI)
在介绍DI概念之前,我们先来看一例子:
/**
* 一个car类
*/
class Car {
private val engine = Engine()
fun start(){
engine.start()
}
fun main(args: Array<String>){
val car = Car()
car.start()
}
}
class Engine{
fun start(){
print("启动引擎")
}
}
在此示例中,Car 类依赖于拥有 Engine 类的一个实例才能运行,这些必须依赖的项被称为依赖项。但这个例子并非是依赖注入,因为Engine类,是Car本身来创建,生命周期由Car管理。那么依赖注入该怎么写呢?下面看看上面代码重构后的例子:
/**
* 依赖注入
*/
class Car(private val engine: Engine) {
fun start(){
engine.start()
}
fun main(args:Array<String>){
val car = Car(Engine())
car.start()
}
}
main 函数使用 Car, Car 依赖于 Engine,,因此应用会创建 Engine 的实例,然后使用它构造 Car 的实例。这种基于 DI 的方法具有以下优势:
1.重用 Car,可以传入不同实现Engine
2.更容易进行单元测试
在安卓中主要有两种不同的注入方式
1.构造函数注入(如上面的例子)。将某个类的依赖项通过构造函数传入。
2.字段注入(或 setter 注入) 。通过字段setter方法将依赖项传入。
Tips:依赖项注入基于控制反转原则,根据该原则,通用代码控制特定代码的执行。
控制反转:控制反转( IoC ) 是一种编程原理。在Java中,可以简单理解为把依赖项(对象)的控制权转交给IOC容器
二、实现依赖注入的方式
第一种方式就是按照上面的例子那样,手动实现依赖注入。这种方式存在两个缺点:
1.样板代码过多,例如造真车,需要依赖引擎、传动装置、底盘,而引擎需要依赖气缸、火花塞等,一层一层依赖下去,会存在大量的样板代码
2.易出错,要保证依赖项在被注入时,已经实例化。
第二种自动依赖注入就是通过一些库自动执行创建和提供依赖项。通常归为两类:
1.基于反射的解决方案,在运行时动态实例化依赖项。
2.静态解决方案,在编译时连接依赖项的代码。
市面上比较成熟的三方库肯定要属Dagger,由 Google 进行维护。Dagger 为您创建和管理依赖关系图,从而便于您在应用中使用 DI。它提供了完全静态和编译时依赖项,解决了基于反射的解决方案的诸多开发和性能问题。
三、依赖注入的替代方案
依赖项注入的替代方法是使用服务定位器,为了更主管理解什么是服务定位器设计模式,举个例子说明
/**
* 服务器定位
*/
object ServiceLocator{
val getEngine = Engine()
}
class Car {
private val engine = ServiceLocator.getEngine
fun start(){
engine.start()
}
fun main(args: Array<String>) {
val car = Car()
car.start()
}
}
与依赖注入(DI)对比:
1.服务定位器所需的依赖项集合使得代码更难测试,因为所有测试都必须与同一全局服务定位器进行交互。
2,很难从外部了解类需要什么。所以,更改 Car 或服务定位器中可用的依赖项可能会导致引用失败,从而导致运行时或测试失败、
3.依赖项(对象)生命周期更难管理
四、Android Studio支持的开源依赖注入库Hilt、Dagger
隶属于Jetpack 库的Hilt。Hilt 通过为项目中的每个 Android 类提供容器并自动为您管理其生命周期,定义了一种在应用中执行 DI 的标准方法。
对比Dagger库
1.Hilt 在热门 DI 库 Dagger 的基础上构建而成,因而能够受益于 Dagger 提供的编译时正确性、运行时性能、可伸缩性和 Android Studio 支持。
2.更容易被市场接受,Dagger学习成本和门槛都太高了,笔者自己也是经历了从入门到放弃,从入门到彻底弃坑的阶段,而Hilt不仅集成了Dagger的优势,还降低了入门门槛,普适性会更高。