【Android Hilt 依賴注入完整教學】系列文章目錄:連結
Youtube 教學頻道:HKT線上教室
在 Android 開發中,你可能經常聽到「依賴注入」(Dependency Injection,簡稱 DI)這個詞。無論是使用 Dagger、Hilt 或 Koin 等框架,依賴注入都是現代 Android 開發中不可或缺的重要概念。讓我們從零開始,深入淺出地了解什麼是依賴注入,以及為什麼我們需要它。
讓我們用一個簡單的例子來說明。假設我們有一個 Car
類別,這個類別需要一個引擎(Engine)來運作:
// 未使用依賴注入的寫法
class Car {
private val engine = Engine()
fun start() {
engine.start()
}
}
這時我們就可以說 Car 類別依賴於 Engine 類別。在實際程式開發的過程中經常會遇到,類似的問題,這段程式碼看起來似乎沒什麼問題,但實際上存在一些設計上的缺陷:
Car
類別與 Engine
類別緊密耦合今天假設我們要支援不同類型的引擎,如:純電動車(ElectricEngine)或是油電混合車(HybridEngine),使用上面的寫法就會遇到困難:
// 不好的改法
class Car {
// private val engine = Engine() // 原本的汽油引擎
// private val electricEngine = ElectricEngine() // 改成純電動車
private val hybridEngine = HybridEngine() // 又改成油電混合動力
fun start() {
// engine.start()
// electricEngine.start()
hybridEngine.start()
}
}
這種作法有幾個明顯的缺點:
Car
類別的程式碼上述問題的根本原因在於程式碼之間產生了高度耦合 (Coupling) 的情況。為了解決這個問題,我們可以運用控制反轉 (Inversion of Control,IoC)的設計原則,將物件的控制權從物件本身移轉到外部的容器或框架。這樣做可以降低程式碼之間的相依性(Dependency),讓系統更容易維護與擴充。
而依賴注入(Dependency Injection,DI)就是實作控制反轉的一種常見手法。它的概念是在建立物件時,將相依的物件從外部注入進去,而不是讓物件自己產生相依物件。透過這種方式,我們就能達到程式碼解耦 (Decoupling) 的目的。
舉個例子,使用依賴注入的方式,A 類別的建構子可以接收 B 的介面當作參數,IoC 容器在產生 A 的物件實體時,會自動將 B 的物件實體注入進去。如此一來,A 只需要依賴 B 的介面定義,而不需要知道 B 的具體實作細節。
// 使用依賴注入的寫法
class Car(private val engine: Engine) {
fun start() {
engine.start()
}
}
class Car(private val engine: Engine)
class Car {
lateinit var engine: Engine
}
使用依賴注入後,我們可以輕鬆替換不同的實作。例如,可以替換成電動引擎:
interface Engine {
fun start()
}
class GasEngine : Engine {
override fun start() { /* 實作汽油引擎啟動邏輯 */ }
}
class ElectricEngine : Engine {
override fun start() { /* 實作電動引擎啟動邏輯 */ }
}
在單元測試中,我們可以輕鬆注入模擬物件:
class CarTest {
@Test
fun testCarStart() {
val mockEngine = MockEngine()
val car = Car(mockEngine)
car.start()
// 驗證測試結果
}
}
由於各個元件之間的耦合度降低,重構程式碼變得更加容易。
在 Android MVVM 架構中,ViewModel 經常需要存取 Repository:
// 未使用依賴注入寫法
class ProductViewModel : ViewModel() {
// 傳統寫法會在 ViewModel 內直接建立 Repository 實例
// 超級常見,超耦合寫法,難以替換實作、難以進行單元測試
private val repository = ProductRepository()
private val _products = MutableLiveData<List<Product>>()
fun getProducts() {
val products = repository.getProducts()
_products.value = products
}
}
// 使用依賴注入的寫法
class ProductViewModel(
private val repository: ProductRepository
) : ViewModel() {
private val _products = MutableLiveData<List<Product>>()
fun getProducts() {
val products = repository.getProducts()
_products.value = products
}
}
在大型專案中,手動管理所有依賴關係會變得非常繁瑣。這就是為什麼我們需要依賴注入框架(如 Hilt)的原因。這些框架可以:
依賴注入是一個強大的設計模式,它能夠:
在接下來的系列文章中,我們將一步一步帶大家由淺入深探討如何在 Android 專案中使用 Hilt 來實現依賴注入!