KMP鸿蒙依赖注入与模块化
项目概述
依赖注入(Dependency Injection, DI)是现代软件开发中的一个关键设计模式。它通过将对象的依赖关系从对象本身转移到外部,使得代码更加灵活、可测试和可维护。在多平台开发中,依赖注入变得更加重要,因为它允许我们在不同平台上提供不同的实现,而应用逻辑无需改变。
本文将详细介绍如何在 KMP 项目中实现依赖注入。我们将展示如何设计一个跨平台的依赖注入框架,如何在各个平台上配置依赖关系,以及如何在鸿蒙系统上使用这个框架。
第一部分:依赖注入的核心概念
为什么需要依赖注入
在没有依赖注入的情况下,对象通常会自己创建它所需的依赖。例如,一个 UserRepository 可能会在其构造函数中创建一个 HttpClient。这样做有几个问题。首先,它使得对象之间的依赖关系不清晰。其次,它使得测试变得困难,因为我们无法轻松地用模拟对象替换真实的依赖。最后,它使得代码的重用变得困难,因为对象的依赖关系是硬编码的。
通过使用依赖注入,我们可以解决这些问题。对象不再自己创建依赖,而是从外部接收依赖。这样做使得对象之间的依赖关系更加清晰,测试变得更加容易,代码的重用也变得更加灵活。
定义依赖容器接口
首先,我们定义一个依赖容器接口,它负责创建和管理对象的依赖关系。
// commonMain/kotlin/com/example/kmp/di/ServiceLocator.kt
interface ServiceLocator {
fun <T> get(key: String): T
fun <T> register(key: String, factory: () -> T)
fun <T> registerSingleton(key: String, instance: T)
}
class DefaultServiceLocator : ServiceLocator {
private val singletons = mutableMapOf<String, Any>()
private val factories = mutableMapOf<String, () -> Any>()
override fun <T> get(key: String): T {
return (singletons[key] ?: factories[key]?.invoke()) as T
}
override fun <T> register(key: String, factory: () -> T) {
factories[key] = factory as () -> Any
}
override fun <T> registerSingleton(key: String, instance: T) {
singletons[key] = instance as Any
}
}
这个服务定位器允许我们注册和检索对象。我们可以为每个请求创建新的对象,也可以注册单例对象。
定义模块
模块是一个组织依赖关系的方式。每个模块负责注册一组相关的依赖。
// commonMain/kotlin/com/example/kmp/di/Module.kt
interface Module {
fun configure(locator: ServiceLocator)
}
class RepositoryModule : Module {
override fun configure(locator: ServiceLocator) {
locator.register("userRepository") {
UserRepository(locator.get("userDao"))
}
locator.register("postRepository") {
PostRepository(locator.get("postDao"))
}
}
}
class NetworkModule : Module {
override fun configure(locator: ServiceLocator) {
locator.registerSingleton("httpClient", HttpClientImpl())
locator.register("networkManager") {
NetworkManager(locator.get("httpClient"))
}
}
}
通过使用模块,我们可以将依赖关系组织成逻辑组,使得代码更加清晰和易于维护。
第二部分:平台特定的依赖注入实现
Android 平台的依赖注入
在 Android 平台上,我们可以使用 Hilt 框架来实现依赖注入。Hilt 是 Google 推荐的 Android 依赖注入框架。
// androidMain/kotlin/com/example/kmp/di/AndroidModule.kt
@Module
@InstallIn(SingletonComponent::class)
object AndroidModule {
@Provides
@Singleton
fun provideHttpClient(): HttpClientInterface {
return AndroidHttpClient()
}
@Provides
@Singleton
fun provideUserDao(httpClient: HttpClientInterface): UserDao {
return AndroidUserDao(httpClient)
}
@Provides
@Singleton
fun provideNetworkManager(userDao: UserDao): NetworkManager {
return NetworkManager(userDao)
}
}
Hilt 使用注解来标记依赖的提供者和注入点,使得依赖注入变得更加简洁和类型安全。
JVM 平台的依赖注入(鸿蒙)
在鸿蒙系统上,我们可以使用 Koin 框架来实现依赖注入。Koin 是一个轻量级的 Kotlin 依赖注入框架。
// jvmMain/kotlin/com/example/kmp/di/JvmModule.kt
val jvmModule = module {
single<HttpClientInterface> { JvmHttpClient() }
single<UserDao> { JvmUserDao(get()) }
single<NetworkManager> { NetworkManager(get()) }
single<UserRepository> { UserRepository(get()) }
}
// 初始化 Koin
fun initializeKoin() {
startKoin {
modules(jvmModule)
}
}
Koin 提供了一个 DSL 来定义依赖关系,使得配置变得更加简洁。
第三部分:编译后的代码形式
Kotlin 依赖注入代码编译为 JavaScript
当我们将 Kotlin 的依赖注入代码编译为 JavaScript 时,会生成以下形式的代码:
// 编译后的 JavaScript (简化版)
var ServiceLocator = function() {
this.singletons = {};
this.factories = {};
};
ServiceLocator.prototype.get = function(key) {
if (this.singletons[key]) {
return this.singletons[key];
}
if (this.factories[key]) {
return this.factories[key]();
}
throw new Error("Service not found: " + key);
};
ServiceLocator.prototype.register = function(key, factory) {
this.factories[key] = factory;
};
ServiceLocator.prototype.registerSingleton = function(key, instance) {
this.singletons[key] = instance;
};
第四部分:鸿蒙系统中的实际应用
在鸿蒙应用中使用依赖注入
在鸿蒙应用中,我们可以使用 Koin 来管理依赖关系。
// HarmonyOS 应用代码
class HarmonyApplication {
init {
initializeKoin()
}
fun createUserRepository(): UserRepository {
return get()
}
}
class HarmonyUserService(
private val userRepository: UserRepository = get()
) {
suspend fun loadUsers() {
val users = userRepository.getUsers()
// 处理用户数据
}
}
通过使用 Koin,我们可以轻松地获取依赖的实例,而无需手动创建它们。
测试中的依赖注入
依赖注入的一个主要优势是使得测试变得更加容易。我们可以轻松地用模拟对象替换真实的依赖。
// 测试代码
class UserRepositoryTest {
private val mockUserDao = MockUserDao()
private val userRepository = UserRepository(mockUserDao)
@Test
fun testGetUsers() {
val users = runBlocking { userRepository.getUsers() }
assertEquals(0, users.size)
}
}
class MockUserDao : UserDao {
override suspend fun insertUser(user: User): Boolean = true
override suspend fun getUserById(id: String): User? = null
override suspend fun getAllUsers(): List<User> = emptyList()
override suspend fun updateUser(user: User): Boolean = true
override suspend fun deleteUser(id: String): Boolean = true
override suspend fun deleteAllUsers() {}
}
通过使用模拟对象,我们可以在不依赖真实数据库或网络的情况下测试我们的代码。
第五部分:高级依赖注入模式
实现作用域
作用域允许我们控制对象的生命周期。例如,我们可能希望为每个用户会话创建一个新的 UserRepository 实例。
// 作用域管理
interface Scope {
fun <T> get(key: String): T
fun close()
}
class ScopedServiceLocator(private val parent: ServiceLocator) : ServiceLocator {
private val scopedInstances = mutableMapOf<String, Any>()
override fun <T> get(key: String): T {
return (scopedInstances[key] ?: parent.get(key)) as T
}
override fun <T> register(key: String, factory: () -> T) {
parent.register(key, factory)
}
override fun <T> registerSingleton(key: String, instance: T) {
scopedInstances[key] = instance as Any
}
}
实现工厂模式
工厂模式允许我们根据条件创建不同的对象。
// 工厂模式
interface HttpClientFactory {
fun createHttpClient(platform: String): HttpClientInterface
}
class DefaultHttpClientFactory : HttpClientFactory {
override fun createHttpClient(platform: String): HttpClientInterface {
return when (platform) {
"android" -> AndroidHttpClient()
"jvm" -> JvmHttpClient()
"web" -> WebHttpClient()
else -> throw IllegalArgumentException("Unknown platform: $platform")
}
}
}

总结
通过本文的学习,我们理解了如何在 KMP 项目中实现跨平台的依赖注入。依赖注入是一个强大的设计模式,它使得代码更加灵活、可测试和可维护。通过使用服务定位器、模块和作用域,我们可以有效地管理应用中的依赖关系。在鸿蒙系统上,我们可以使用 Koin 框架来简化依赖注入的配置和使用。
更多推荐


所有评论(0)