Kotlin 依賴注入實戰指南:解耦程式碼與靈活替換實作

Sharon MaiSharon Mai
2 min read

什麼是依賴?

  • 廣義:「程式碼直接相依於某個具體實作」

      class ChefService {
          // 依賴具體類別
          private val marketA = MarketA()
    
          // 依賴具體函數/方法
          fun makeSweet() {
              FileUtils.readFile("recipe.txt")
          }
    
          // 依賴具體實作細節
          fun getIngredients() {
                  // 依賴特定URL
              val url = "<https://marketA.com/api>"  
          }
      }
    
  • 在依賴注入情境來說依賴:「一個類別直接創建/使用另一個類別」

      class ChefService {
          // 直接創建實例 = 依賴
          private val marketA = MarketA()
    
          fun makeSweet() {
              // 直接使用實例 = 依賴
              val ingredients = marketA.buy() // 使用 MarketA 實例的 buy() 方法
          }
      }
    

直接依賴的問題

  • 測試困難

    • 單元測試需要真實的資料庫/網路環境

    • 無法替換成測試用的模擬物件

  • 靈活性差

    • 例:從 Firebase API 改成 Backend API,想換實作方式,需要修改相關程式碼
  • 維護性差

    • 實作細節改變,需要修改所有相依的程式碼

依賴小結:

  • 依賴 = 直接相依某個具體實作

  • 依賴注入的依賴 = 在類別中 另個類別直接創建/使用另一個類別

  • 造成程式碼難以測試、替換和維護

  • 需要一個更好的方式來管理這些依賴關係...


改善方法:依賴反轉與注入

依賴反轉是設計原則,而依賴注入是實現此原則的方式之一

  • 概念說明

    • 依賴反轉就是將原本依賴某個實作改成依賴抽象,通過介面解耦
  • 依賴反轉原則(DIP)是 SOLID 的一環:

依賴反轉的實現之一:依賴注入

  • 什麼是依賴注入

    • 依賴注入是實現依賴反轉的方法之一,透過「由外部提供依賴」

      • 也就是將實作決定權,從類別內部移到外部來決定
    • 注入的方式之一 Constructor Injection(建構子注入)

為什麼要用依賴注入:解耦,方便替換實作

直接依賴問題依賴注入解法
private val marketA = MarketA()class ChefService(private val market: Market)
難以替換實作透過介面抽象,由外部注入實作

怎麼做依賴注入

情境說明

在開發初期常見需要的假資料為例。 測試模式時,使用的資料來源為假資料,非測試模式時則需實際API後端為真實資料來源

Hilt 實作示範

依賴注入本質是一種設計模式,不依賴框架,Hilt 是框架來達到依賴注入的解法之一。

  1. 使用 Hilt 模組來決定要注入哪個實作

     @Module
     @InstallIn(SingletonComponent::class)
     class RepositoryModule {
         @Provides
         @Singleton
         fun provideNoteRepository(
             appConfiguration: AppConfiguration,
             dataSourceImpl: RemoteNoteDataSourceImpl,
         ): NoteRepository {
             return if (appConfiguration.isTestMode == false) { 
                 FakeNoteRepositoryImpl() // <--實作的創建
             } else {
                 NoteRepositoryImpl(dataSourceImpl)
             }
         }
     }
    
  2. 使用之處只需要依賴介面

     @HiltViewModel
     class NoteListViewModel @Inject constructor(
         private val repository: NoteRepository // <--外部注入,由外部決定哪個為實作
     ) : ViewModel() {
    
             fun getNotes() {
             noteRepository.getNotes()  // 不需要知道具體是哪個實作
         }
     }
    

總結

  • 依賴反轉:通過介面解耦

  • 依賴注入:可以通過 Hilt 框架來管理具體實作的創建和注入

0
Subscribe to my newsletter

Read articles from Sharon Mai directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Sharon Mai
Sharon Mai

Hi, I'm Sharon