渐进式编程课
KMP 目录
第 13 课2026.07.29

构造器注入解决了“类如何接收依赖”,但仍需要一个地方创建真实的 Ktor Client、数据源、Repository 和用例。这个集中组装对象图的位置通常称为 Composition Root(组合根)。

第 13 课:手动组装共享依赖

  • 日期:2026-07-29
  • 课程序号:13
  • 知识点:在 Composition Root 中集中创建并连接共享对象

用途

构造器注入解决了“类如何接收依赖”,但仍需要一个地方创建真实的 Ktor Client、数据源、Repository 和用例。这个集中组装对象图的位置通常称为 Composition Root(组合根)。

核心概念

  • 对象只声明自己需要什么,组合根负责决定使用哪个具体实现。
  • Ktor Client、Repository 等长生命周期对象应集中创建并复用。
  • 页面接收已经组装好的用例或 Repository,不在页面里重复创建整条依赖链。
  • 需要释放的共享资源由拥有组合根的平台入口统一关闭。
  • 小型项目先用普通 Kotlin 类手动组装即可,不必立即引入 DI 框架。

示例代码

假设前几课已经实现了 KtorUserApi、DefaultUserRepository、GetUserLabel 和 createHttpClient()。

在 commonMain 中集中组装它们:

kotlin
import io.ktor.client.HttpClient

class AppContainer(
    private val httpClient: HttpClient,
) {
    private val userApi: UserApi = KtorUserApi(httpClient)

    val userRepository: UserRepository =
        DefaultUserRepository(userApi)

    val getUserLabel: GetUserLabel =
        GetUserLabel(userRepository)

    fun close() {
        httpClient.close()
    }
}

fun createAppContainer(): AppContainer {
    return AppContainer(
        httpClient = createHttpClient(),
    )
}

平台应用入口只创建一次容器,再把需要的依赖传给页面:

kotlin
val appContainer = createAppContainer()

// 页面或状态持有者接收已经组装好的用例
val getUserLabel = appContainer.getUserLabel

应用生命周期结束时,由持有容器的平台入口调用:

kotlin
appContainer.close()

这样对象的创建位置、复用范围和释放责任都清晰可见。

5 分钟练习

假设已经有 GetUser 用例,请把它加入 AppContainer,并复用现有的 userRepository。

参考答案

kotlin
class AppContainer(
    private val httpClient: HttpClient,
) {
    private val userApi: UserApi = KtorUserApi(httpClient)

    val userRepository: UserRepository =
        DefaultUserRepository(userApi)

    val getUserLabel: GetUserLabel =
        GetUserLabel(userRepository)

    val getUser: GetUser =
        GetUser(userRepository)

    fun close() {
        httpClient.close()
    }
}

两个用例共享同一个 Repository,Repository 也共享同一个 HttpClient,不会因页面重建而重复创建网络客户端。

与上一课的联系

上一课介绍了通过构造器传入依赖;本课补齐对象图的另一半:在应用入口集中创建真实实现并连接它们。后续页面只消费组装结果,不承担数据层对象的创建责任。