You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何按Clean Architecture要求隔离Dagger2,实现业务逻辑无感知?

如何隔离Dagger2,让业务逻辑完全不感知它

完全理解你的困惑——我当初在把Dagger2和Clean Architecture结合的时候也纠结过这个点!Robert Martin强调的「DI框架是实现细节,要远离业务核心层」确实是能落地的,核心思路就是让业务层只依赖抽象的注入接口,把Dagger的实现完全封装在框架层,具体可以这么做:

1. 定义抽象的注入接口(业务层唯一依赖的DI相关类)

首先,在靠近框架层的地方创建一个纯抽象的注入接口,里面只暴露业务层需要的依赖获取方法,完全不涉及任何Dagger的注解或类。业务层只会和这个接口打交道,根本不知道背后是Dagger在工作。

比如:

// 这个接口属于框架层的抽象,业务层可以安全依赖它
interface AppInjector {
    // 暴露业务层需要的用例、仓库等依赖
    fun getLoginUseCase(): LoginUseCase
    fun getUserRepository(): UserRepository
}

2. 让Dagger Component实现这个抽象接口

接下来,让你的Dagger Component去实现上面的抽象接口,把Dagger的所有实现细节都封装在这里。业务层看不到这个Component的存在,它只在主模块(比如Application)里被初始化。

举个例子:

// 这个类完全属于框架层,只有主模块会引用它
@Singleton
@Component(modules = [RepositoryModule::class, UseCaseModule::class])
interface AppComponent : AppInjector {
    @Component.Factory
    interface Factory {
        fun create(@BindsInstance context: Context): AppComponent
    }
}

对应的Module也只负责提供具体实现,同样属于框架层:

@Module
class RepositoryModule {
    @Provides
    @Singleton
    fun provideUserRepository(): UserRepository {
        return UserRepositoryImpl()
    }
}

@Module
class UseCaseModule {
    @Provides
    fun provideLoginUseCase(repo: UserRepository): LoginUseCase {
        return LoginUseCase(repo)
    }
}

3. 在主模块初始化Dagger,传递抽象接口实例

在你的Application类(主模块)里,初始化Dagger Component,并把它赋值给抽象的AppInjector类型的变量。这样上层代码拿到的永远是抽象接口,而非具体的Dagger实现。

class MyApp : Application() {
    lateinit var injector: AppInjector

    override fun onCreate() {
        super.onCreate()
        // 这里是唯一直接引用Dagger的地方
        injector = DaggerAppComponent.factory().create(this)
    }
}

4. 业务层完全脱离Dagger,只依赖抽象和构造注入

最关键的一步:你的实体、用例、抽象仓库等业务核心类,绝对不能出现任何Dagger注解(比如@Inject)或引用Dagger相关类。所有依赖都通过构造函数传入,依赖的实例由抽象的AppInjector提供。

比如用例类:

// 纯业务逻辑类,没有任何Dagger痕迹
class LoginUseCase(private val userRepository: UserRepository) {
    fun execute(username: String, password: String): Result<User> {
        // 这里只处理业务逻辑,完全不关心依赖从哪来
        return userRepository.login(username, password)
    }
}

在需要使用用例的地方(比如UI层),只通过抽象的AppInjector获取:

class LoginViewModel : ViewModel() {
    // 只依赖抽象的AppInjector,不知道Dagger的存在
    private val loginUseCase = (MyApp.instance.injector).getLoginUseCase()

    fun login(username: String, password: String) {
        val result = loginUseCase.execute(username, password)
        // 处理登录结果
    }
}

额外的好处

这么做除了符合Clean Architecture的原则,还有两个实用优势:

  • 替换DI框架成本极低:如果哪天想换成Hilt、Koin甚至手动DI,只需要重新实现AppInjector接口,业务层代码一行都不用改。
  • 测试更简单:写单元测试时,你可以快速实现一个Mock版的AppInjector,提供Mock的依赖,完全不需要启动Dagger的注入流程。

内容的提问来源于stack exchange,提问作者1048576

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.19 03:11:58