文章目录
依赖注入
1、引入 – 为何需要依赖注入
1.1 做一台咖啡机
我们有一台咖啡机(CoffeeMaker
),组成的零件有泵浦(Pump
)和加热器(Heater
)。
咖啡的制作流程如下:
首先,加热器代码如下:
class ElectricHeater{
private var heating = false
fun isHot(): Boolean {
return heating
}
fun off() {
heating = false
}
fun on() {
println("~ ~ ~ heating ~ ~ ~")
heating = true
}
}
接着是泵浦,是个虹吸装置,并且装置具有防呆机制,防止阿呆忘记加热就打水:
class Thermosiphon(val heater: ElectricHeater){
fun pump() {
if(heater.isHot()){
println("=> => pumping => =>")
}
}
}
最后,泵浦和加热器组合得到咖啡机:
class Coffeemaker{
private val heater = ElectricHeater()
private val pump = Thermosiphon(heater)
fun brew(){
heater.on()
pump.pump()
println(" [_]P coffee! [_]P ")
heater.off()
}
}
1.2 依赖关系
从上面UML
类图可以知道,咖啡机依赖了加热器和泵浦,而泵浦又依赖了加热器。加热器没有泵浦和咖啡机可以独立工作,但是咖啡机和泵浦却离不开加热器。这样,高层模组咖啡机依赖于低级模组泵浦和加热器。
再来看一下Coffeemaker
这个类。当我们需要一台咖啡机时,同时需要创建出全新的泵浦和加热器,这样泵浦、加热器和咖啡机高耦合了,这样会造成什么问题呢?
- 问题一:某个收集咖啡机的爱好者想要拥有多台咖啡机,但不想要拥有很多的泵浦和加热器时,这样,因为每一台咖啡机都自带泵浦和加热器,造成资源浪费。
- 问题二:厂商在研发咖啡机的時候,想要拿个假的加热器跟泵浦來试试看咖啡机作流程有沒有错误时。在这样的情况下,沒办法在不动
Coffeemaker
配方 (程式码) 的情況下完成。 - 问题三:有一天,加热器改进了,把
on
方法改了open
,咖啡机必须要配合加热器跟着改版才能顺利运作。。
1.3 改进生产工艺
好,那我们来改进咖啡机的生产工艺,解决这些问题。
1.3.1 解开依赖
首先解决问题一,只要调整一下生产线就可以了:将泵浦和加热器委外生产,同时将咖啡机模组化。
改进的配方如下:
class Coffeemaker(
private val heater:ElectricHeater,
private val pump:Thermosiphon
) {
fun brew() {
heater.on()
pump.pump()
println(" [_]P coffee! [_]P ")
heater.off()
}
}
很好,现在咖啡机收集狂可以只拥有一组加热器跟泵浦,但可以拥有很多台咖啡机了。
class TheManHavingManyCoffeemakers {
val heater = ElectricHeater()
val pump = Thermosiphon(heater)
val cofeemaker1 = Coffeemaker(heater,pump)
val cofeemaker2 = Coffeemaker(heater,pump)
...
}
1.3.2 依赖反转
接下来解决二、三、四问题。咖啡机的厂商想了想,决定指定标准规则 (interface
) ,规定加热器跟泵浦的规格,比如说必须要有哪些功能等等。具体如何实现这个功能则不在标准之內。于是,咖啡机以及其他组件都必须按照这个标准来设计。
interface Heater{
fun on()
fun off()
fun isHot():Boolean
}
interface Pump{
fun pump()
}
咖啡机配方改变为下面的流程:
class Coffeemaker(
private val heater: Heater,
private val pump: Pump
){
fun brew(){
heater.on()
pump.pump()
println(" [_]P coffee! [_]P ")
heater.off()
}
}
在这里,所有来自外部的零件必须符合规范(interface
)才能采购用来生产咖啡机,这样任何厂商都可以生产泵浦和加热器并将它们用于咖啡机的生产中,而不必纠结于是ElectricHeater
还是其它的加热器,比如换成:MagicHeater
也不是问题。
class MagicHeater : Heater {
private var heating = false
override fun isHot(): Boolean {
return heating
}
override fun off() {
heating = false
}
override fun on() {
println("~ ~ ~ This is Magic ~ ~ ~")
println("~ ~ ~ heating ~ ~ ~")
println("~ ~ ~ magic ~ ~ ~")
heating = true
}
}
但是如果不符合规范,将on
改成open
这样的改变,由于不符合市场规则,会被咖啡机业界淘汰。原来,咖啡机必须要配合低层模组來设计,现在高层模组以及底层模组都必须配合interface
来设计。在软件工程中,这种方式被称为依赖反转等等。
1.3.3 提升工艺,降低成本
现在要制造咖啡机,至少需要三条供应链:生产咖啡机、生产泵浦、生产加热器。也就是说,每当在不同的地方要生出一台咖啡机时,我们也必须产出至少一组加热器和泵浦。
class PeopleInBeijing {
val heater = ElectricHeater()
val pump = Thermosiphon(heater)
val cofeemaker = Coffeemaker(heater,pump)
...
}
class PeopleInShanghai {
val heater = ElectricHeater()
val pump = Thermosiphon(heater)
val cofeemaker = Coffeemaker(heater,pump)
...
}
...
这样比较麻烦,需要写重复的代码,相当于手工制作这些产品。更麻烦的是,加热器和泵浦都暴露在外面,使用者可以随时取得他们,拿去做任何事情,比如拿加热器烫衣服、甚至烫人。我们引入一批专门的机器(Injector/Container...
),这样的机器负责将咖啡机需要的加热器和泵浦生产出来并安装到位。
class Injector{
private val heater = ElectricHeater()
private val pump = Thermosiphon(heater)
fun provideHeater() : Heater{
return heater
}
fun providePump() : Pump{
return pump
}
}
class Coffeemaker(injector:Injector)
{
private val heater = injector.provideHeater()
private val pump = injector.providePump()
fun brew(){
heater.on()
pump.pump()
println(" [_]P coffee! [_]P ")
heater.off()
}
}
当咖啡机的依赖零件需要更换成其它厂商的零件或者测试的伪零件时,只需要改掉Injector
就可以了。为了更进一步,可以将Injector
机器制定规则(Interface)。
class CoffeeMakerProvider{
companion object{
fun provideCoffeeMaker() = Coffeemaker(Injector())
}
}
这样一来,随时随地都可以取得一件新的咖啡机,并且内部组件也被包装起来,不随便展示给别人使用了。也可以让Injector
用单例来实现,这样,每次提供的加热器和泵浦都是同一台机器,达到重用、节省资源的目的。
1.3.4 引入工艺,降低成本
前面我们制作了Injector
机器,提升了制作工艺,降低成本。有时,虽然我们已经优化地比较好了,但是,有些更好的工艺可以引入,比如Dagger
依赖注入框架。
在使用Kotlin
的环境中:
dependencies {
...
implementation 'com.google.dagger:dagger:2.17'
kapt 'com.google.dagger:dagger-compiler:2.17'
...
}
-
Inject。首先,我们改造一下需要依赖注入的泵浦以及咖啡机,把需要被注入的物件 (加热器) 用
@Inject
标记出来。在Dagger2
中,@Inject
可以用来标记构造函数、属性以及函数。class Thermosiphon @Inject constructor(val heater: Heater) : Pump { override fun pump() { if(heater.isHot()){ println("=> => pumping => =>") } } } class CoffeeMaker @Inject constructor(private val heater: Heater, private val pump: Pump) { fun brew(){ heater.on() pump.pump() println(" [_]P coffee! [_]P ") heater.off() } }
-
Module。接下建立
Module
,它的角色相当于前面Injector
的角色,负责生产零件。由于Thermosiphon
也依赖于Heater
,因此泵浦的提供方法不使用@Provides
,而是用@Binds
将提供泵浦的方法以及我们选择的泵浦类(Thermosiphon
)连结起来。最后使用includes
将两个模组连接起来,Dagger
会将同一个加热器同时注入到咖啡机以及泵浦中。@Module(includes = [PumpModule::class]) class CoffeeMakerModule { @Provides fun provideHeater():Heater{ return ElectricHeater() } } @Module abstract class PumpModule{ @Binds abstract fun providePump(pump: Thermosiphon):Pump }
-
Component。最后则是生产咖啡机的产线
Component
,相当与前面的CoffeeMakerProvider
。我们只要告诉Dagger
要使用的模组以及要生产的咖啡机类別就可以了。@Component(modules = [CoffeeMakerModule::class]) interface CoffeeComponent { fun provideCoffeeMaker() : CoffeeMaker }
-
生产咖啡机。东西都准备好了,之后,
build
这件产品。Dagger2
会根据这些注解,为我们生产组件及产品。fun main(){ val coffeeProvider = DaggerCoffeeComponent.builder().build() val coffeeMaker = coffeeProvider.provideCoffeeMaker() coffeeMaker.brew() }