什么是依賴(lài)注入?
依賴(lài)注入(Dependency Injection),在編程中被廣泛使用,非常適用于Android開(kāi)發(fā)。作為一門(mén)應(yīng)用架構(gòu)的基礎(chǔ)科學(xué),為應(yīng)用的良性發(fā)展提供了非常優(yōu)秀的支持。
實(shí)現(xiàn)依賴(lài)注入,可用為我們帶來(lái)這些好處:
- 重用代碼
- 易于重構(gòu)
- 易于測(cè)試
我們都知道,在OOP開(kāi)發(fā)中,類(lèi)往往需要引用其他類(lèi)。例如,我們生產(chǎn)一個(gè)Car,總是離不開(kāi)Engine,這被成為依賴(lài)關(guān)系。那么思考一下,Car要如何獲取自己所需要的Engine呢?我們往往采用這些方式:
- 在類(lèi)中構(gòu)造所需依賴(lài)
- 從其它地方獲取
- 以參數(shù)方式提供
而依賴(lài)注入,就是以參數(shù)方式提供依賴(lài)的,簡(jiǎn)單來(lái)看下代碼:
class Engine {
fun start() {
println("Engine start!")
}
}
// 無(wú)依賴(lài)注入
class Car {
fun start() {
val engine = Engine()
engine.start()
}
}
// 構(gòu)造函數(shù)注入
class Car(private val engine: Engine) {
fun start() {
engine.start()
}
}
fun main() {
val engine = Engine()
val car = Car(engine)
car.start()
}
// setter注入
class Car {
lateinit var engine: Engine
fun start() {
engine.start()
}
}
fun main() {
val engine = Engine()
val car = Car()
car.engine = engine
car.start()
}
什么是Dagger?
在上面,我們已經(jīng)手動(dòng)實(shí)現(xiàn)了最基礎(chǔ)的依賴(lài)注入案例。你可能會(huì)說(shuō),這么簡(jiǎn)單啊,幾行代碼而已的事嘛??扇绻@個(gè)Car示例中,慢慢的加入更多的需求,可能需要引擎、傳動(dòng)裝置、底盤(pán)以及其他部件;而要制造引擎,則需要汽缸和火花塞,依賴(lài)的類(lèi)變得越來(lái)越多,再這樣手動(dòng)實(shí)現(xiàn)依賴(lài)注入就變得非常困難。這時(shí),Dagger就該登場(chǎng)了。
Dagger通過(guò)注解的方式,在程序編譯過(guò)程中生成依賴(lài)注入所需要的靜態(tài)代碼,只要您聲明類(lèi)的依賴(lài)項(xiàng)并指定如何使用注釋滿(mǎn)足它們的依賴(lài)關(guān)系,Dagger 便會(huì)在構(gòu)建時(shí)自動(dòng)執(zhí)行以上所有操作。Dagger 生成的代碼與您手動(dòng)編寫(xiě)的代碼類(lèi)似。在內(nèi)部,Dagger 會(huì)創(chuàng)建一個(gè)對(duì)象圖,然后它可以參考該圖來(lái)找到提供類(lèi)實(shí)例的方式。對(duì)于圖中的每個(gè)類(lèi),Dagger 都會(huì)生成一個(gè) factory類(lèi),它會(huì)使用該類(lèi)在內(nèi)部獲取該類(lèi)型的實(shí)例。
Dagger簡(jiǎn)單案例
我們還是使用上面的汽車(chē)案例,用Dagger來(lái)對(duì)它進(jìn)行改造。
Inject
// Inject 告訴Dagger如何創(chuàng)建實(shí)例
class Car @Inject constructor(
private val engine: Engine
) {
fun start() {
engine.start()
}
}
Module
// Module 提供創(chuàng)建實(shí)例所需要的參數(shù)
@Module
class CarModule {
@Provides
fun provideEngine(): Engine {
return Engine()
}
}
Component
// Component 將創(chuàng)建出的實(shí)例提供給需要的地方
@Singleton
@Component(modules = [CarModule::class])
interface CarComponent {
fun inject(activity: MainActivity)
}
Use
class MainActivity : AppCompatActivity() {
@Inject
lateinit var car: Car
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 創(chuàng)建Component
DaggerCarComponent.builder()
.carModule(CarModule())
.build()
.inject(this)
car.start()
}
}
Hilt實(shí)踐之MVVM
清楚了Dagger最最基本的依賴(lài)注入原理之后,我們可以直接步入Google最新推出的專(zhuān)為Android而生的依賴(lài)注入庫(kù)Hilt,而不用再去糾結(jié)去Dagger在Android中配置的細(xì)枝末節(jié),極大的降低了學(xué)習(xí)成本 ,提升開(kāi)發(fā)效率。
這里直接看項(xiàng)目源碼。
Kotlin依賴(lài)注入之Koin
相較于復(fù)雜的Dagger,在Kotlin中還有一個(gè)依賴(lài)注入框架koin。它運(yùn)用了大量的Kotlin特性,將依賴(lài)注入用dsl(領(lǐng)域特定語(yǔ)言)的方式實(shí)現(xiàn),使依賴(lài)注入變得異常的簡(jiǎn)單。同樣,我們準(zhǔn)備了和Hilt相同邏輯的示例項(xiàng)目。
總結(jié)
到這里,我們已經(jīng)介紹了Android中的依賴(lài)注入方式,相信你對(duì)依賴(lài)注入也有了一定的認(rèn)識(shí)和思考,這里我們回到開(kāi)始的地方,結(jié)合實(shí)例重新去思考我們?cè)陂_(kāi)頭提到的依賴(lài)注入帶來(lái)的三個(gè)好處,這些好處到底是如何帶來(lái)的?
重用代碼:實(shí)例中我們的
MainViewModel中依賴(lài)了UserRepository,我們?nèi)绻皇褂靡蕾?lài)注入,就會(huì)new一個(gè)UserRepository對(duì)象。這樣我們之后每次新建頁(yè)面的ViewModel都需要new一個(gè)UserRepository。易于重構(gòu):試想我們的App中的
User信息不只是從Web獲取,還希望在本地緩存并且在網(wǎng)絡(luò)異常是從本地獲取,我們就需要在UserRepository新增構(gòu)造參數(shù)database: UserDatabase,借助于依賴(lài)注入,我們只需要重構(gòu)repository中的內(nèi)容,ViewModel中的實(shí)現(xiàn)完全不會(huì)受到任何影響。易于測(cè)試:這里設(shè)計(jì)到測(cè)試的內(nèi)容,比如我們測(cè)試
UserRepository的時(shí)候,需要mock依賴(lài)的對(duì)象,如果每次都需要new一個(gè)對(duì)象,測(cè)試將會(huì)變得難以進(jìn)行。