限定詞 (Qualifiers)
限定詞允許您在 Koin 模組中區分相同型別的多個定義。
什麼時候需要限定詞
您在以下情況需要限定詞:
- 您有同一個介面的多種實作
- 您需要同一個型別的不同配置
- 您想要區分具有不同用途的執行個體
kotlin
// 沒有限定詞 - 衝突!
val networkModule = module {
single { OkHttpClient.Builder()...build() }
single { OkHttpClient.Builder()...build() } // 哪一個?
}具名限定詞 (Named Qualifiers)
使用 named() 來區分定義:
定義
kotlin
import org.koin.core.qualifier.named
val networkModule = module {
single(named("encrypted")) {
OkHttpClient.Builder()
.addInterceptor(EncryptionInterceptor())
.build()
}
single(named("logging")) {
OkHttpClient.Builder()
.addInterceptor(HttpLoggingInterceptor())
.build()
}
}注入
kotlin
// 在模組定義中
val apiModule = module {
single {
ApiService(
encryptedClient = get(named("encrypted")),
loggingClient = get(named("logging"))
)
}
}
// 使用 KoinComponent
class MyService : KoinComponent {
private val encryptedClient: OkHttpClient by inject(named("encrypted"))
}搭配註解
kotlin
import org.koin.core.annotation.Named
import org.koin.core.annotation.Single
@Single
@Named("encrypted")
class EncryptedHttpClient : OkHttpClient()
@Single
@Named("logging")
class LoggingHttpClient : OkHttpClient()
@Single
class ApiService(
@Named("encrypted") private val encryptedClient: OkHttpClient,
@Named("logging") private val loggingClient: OkHttpClient
)NOTE
對於編譯器外掛程式 DSL 和經典 DSL 自動裝配 (singleOf、factoryOf),限定詞無法自動解析。當定義需要限定詞時,請使用搭配 Lambda 的經典 DSL 或註解。
型別安全限定詞
使用型別
將任何型別搭配 named<T>() 作為限定詞使用:
kotlin
// 定義限定詞型別
object EncryptedClient
object LoggingClient
val networkModule = module {
single(named<EncryptedClient>()) {
OkHttpClient.Builder()
.addInterceptor(EncryptionInterceptor())
.build()
}
single(named<LoggingClient>()) {
OkHttpClient.Builder()
.addInterceptor(HttpLoggingInterceptor())
.build()
}
}
// 注入
val client: OkHttpClient = get(named<EncryptedClient>())使用列舉 (Enums)
為了獲得更好的 IDE 支援,請使用列舉:
kotlin
enum class NetworkClient {
ENCRYPTED,
LOGGING,
FAST
}
val networkModule = module {
single(named(NetworkClient.ENCRYPTED)) {
OkHttpClient.Builder()
.addInterceptor(EncryptionInterceptor())
.build()
}
single(named(NetworkClient.LOGGING)) {
OkHttpClient.Builder()
.addInterceptor(HttpLoggingInterceptor())
.build()
}
}
// 注入 - 不可能發生拼寫錯誤!
val client: OkHttpClient = get(named(NetworkClient.ENCRYPTED))型別安全限定詞的優點:
- 編譯時期型別安全
- 沒有字串拼寫錯誤
- IDE 自動補全與重構支援
JSR-330 @Qualifier
Koin 支援標準的 JSR-330 @Qualifier 註解:
kotlin
import jakarta.inject.Qualifier
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class IoDispatcher
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class EncryptedClient
// 在定義中使用
@Single
@IoDispatcher
fun provideIoDispatcher(): CoroutineDispatcher = Dispatchers.IO
// 在注入中使用
@Single
class MyRepository(
@IoDispatcher private val ioDispatcher: CoroutineDispatcher
)常見使用案例
多個 API 版本
kotlin
val networkModule = module {
single(named("api_v1")) {
Retrofit.Builder()
.baseUrl("https://api.example.com/v1/")
.build()
}
single(named("api_v2")) {
Retrofit.Builder()
.baseUrl("https://api.example.com/v2/")
.build()
}
}不同的超時配置
kotlin
val networkModule = module {
single(named("fast")) {
OkHttpClient.Builder()
.connectTimeout(5, TimeUnit.SECONDS)
.build()
}
single(named("slow")) {
OkHttpClient.Builder()
.connectTimeout(30, TimeUnit.SECONDS)
.build()
}
}環境配置
kotlin
val configModule = module {
single(named("debug")) {
AppConfig(apiUrl = "https://dev.example.com", loggingEnabled = true)
}
single(named("release")) {
AppConfig(apiUrl = "https://api.example.com", loggingEnabled = false)
}
// 根據環境選擇
single<AppConfig> {
if (isDebug) get(named("debug")) else get(named("release"))
}
}最佳實務
1. 謹慎使用限定詞
kotlin
// 佳 - 僅在必要時使用限定詞
val appModule = module {
single { UserRepository(get()) } // 不需要限定詞
single { AuthRepository(get()) } // 型別不同
}
// 過度限定 - 請避免
val appModule = module {
single(named("user_repository")) { UserRepository(get()) }
single(named("auth_repository")) { AuthRepository(get()) }
// 不必要 - 型別已經不同了
}2. 優先考慮型別區分
kotlin
// 較佳 - 使用不同的型別
interface EncryptedHttpClient
interface LoggingHttpClient
class EncryptedOkHttpClient : OkHttpClient(), EncryptedHttpClient
class LoggingOkHttpClient : OkHttpClient(), LoggingHttpClient
val networkModule = module {
single<EncryptedHttpClient> { EncryptedOkHttpClient() }
single<LoggingHttpClient> { LoggingOkHttpClient() }
}3. 避免限定詞鏈
kotlin
// 差 - 複雜的限定詞相依性
val badModule = module {
single(named("a")) { A() }
single(named("b")) { B(get(named("a"))) }
single(named("c")) { C(get(named("b"))) }
}
// 較佳 - 扁平化或使用不同的型別
val goodModule = module {
single { A() }
single { B(get()) }
single { C(get()) }
}4. 為限定詞編寫文件
kotlin
val networkModule = module {
// 用於具有加密功能的安全 API 呼叫的用戶端
single(named("encrypted")) { ... }
// 用於具有完整請求/回應記錄偵錯的用戶端
single(named("logging")) { ... }
}命名慣例
基於字串
kotlin
// 佳 - 具描述性,小寫並使用底線
single(named("encrypted_client")) { ... }
single(named("user_database")) { ... }
single(named("api_v2")) { ... }
// 避免 - 不清晰或不一致
single(named("client1")) { ... } // "1" 代表什麼意思?基於列舉 (Enum-Based)
kotlin
// 佳 - 清晰的列舉名稱
enum class DatabaseType {
USER_DATA,
CACHE,
ANALYTICS
}
enum class ApiVersion {
V1, V2, V3
}常見陷阱
注入時忘記限定詞
kotlin
val module = module {
single(named("encrypted")) { OkHttpClient() }
}
val repoModule = module {
single {
MyRepository(get()) // ❌ 錯誤:沒有 OkHttpClient 的定義
// 應為:get(named("encrypted"))
}
}限定詞名稱不符
kotlin
val module = module {
single(named("encrypted_client")) { OkHttpClient() }
}
val repoModule = module {
single {
ApiService(
get(named("encrypted")) // ❌ 拼寫錯誤!應為 "encrypted_client"
)
}
}請使用列舉限定詞來避免拼寫錯誤!
