依赖注入 以及 控制反转
这也是解耦合的一种方式,
核心概念
- DIP(依赖反转原则):高层模块不应该依赖低层模块,二者都应依赖抽象(接口/抽象类);抽象不应依赖细节,细节应依赖抽象。
- IoC(控制反转):不由类自己去
new出其依赖,而是由外部容器或代码负责提供依赖(反转控制权)。 - 依赖注入(DI):实现 IoC 的一种方式 —— 把依赖“注入”给消费者(构造器注入、属性/字段注入、方法注入)。
为什么要用(优点)
- 更易测试(可替换 mock/假实现)
- 降耦合、提高可维护性、易扩展
- 更清晰地依赖边界与生命周期管理(配合 DI 框架更强大)

常见实现方式与示例
1) 手动依赖注入(最简单、适合小项目 / 测试)
思想:通过构造函数把依赖传入(Constructor Injection)。简单明了,利于单元测试。
kotlin
// 接口与实现
interface UserRepository {
fun getUser(id: String): User
}
class UserRepositoryImpl(private val api: UserApi) : UserRepository {
override fun getUser(id: String) = api.fetch(id)
}
// 使用方(ViewModel)
class UserViewModel(private val userRepository: UserRepository) : ViewModel() {
fun load(id: String) { /* use userRepository */ }
}
// 在 Activity/Fragment/Application 中组装依赖
class MyApplication : Application() {
// 简单工厂/单例
val userApi by lazy { UserApi() }
val userRepository by lazy { UserRepositoryImpl(userApi) }
}
class MainActivity : AppCompatActivity() {
private lateinit var viewModel: UserViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val repo = (application as MyApplication).userRepository
viewModel = UserViewModel(repo) // 注入
}
}优点:简单、可控、易理解。
缺点:在复杂对象图或需要作用域管理时会变臃肿(许多
by lazy/工厂代码)。Google的例子就说得很清晰了,Google举了一个例子,大概意思就是说,你要new一辆车的时候,你需要把汽车的引擎、轮胎等等内容,都要放进去。
而汽车的引擎,很可能又依赖于其他物件的形成,那么这种new到底要new到什么时候呢?
回到Android的例子,以MVVM来说,ViewModel由Repository组成,而Repository又由UseCase组成。
kotlinclass HomefeedViewModel( private val repo: HomefeedRepo ) { // ... } class HomefeedRepo( private val repo: HomefeedRepo ) { // ... } class HomefeedUseCase( private val service: HomefeedService ) { // ... } class HomefeedService { // ... } class HomefeedViewModelFactory( private val homefeedRepo: HomefeedRepo, ) : ViewModelProvider.Factory { override fun <T : ViewModel> create(modelClass: Class<T>): T { if (modelClass.isAssignableFrom(HomefeedViewModel::class.java)) { @Suppress("UNCHECKED_CAST") return HomefeedViewModel(homefeedRepo) as T } throw IllegalArgumentException("Unknown ViewModel class") } }构建ViewModel
kotlin// 注意看这一行的代码,这里面疯狂嵌套new了一大堆的东西,非常复杂以及嵌套非常深。 val homefeedRepo = HomefeedRepo(HomefeedUseCase(HomefeedService())) val factory = HomefeedViewModelFactory(homefeedRepo) val viewModel = ViewModelProvider(this, factory)[HomefeedViewModel::class.java]- 这种疯狂嵌套的构造函数,看了头皮都要发麻了,复杂程度非常大。
2) Service Locator(工厂/注册表)—— 注意它是有争议的模式
思想:全局注册/查找依赖(例如单例注册),调用方从容器拿依赖。比手动组装少些样板,但会隐藏依赖(不利于可测试性/明确的依赖契约)。
kotlin
object ServiceLocator {
private val singletons = mutableMapOf<Class<*>, Any>()
fun <T : Any> register(clazz: Class<T>, impl: T) { singletons[clazz] = impl }
@Suppress("UNCHECKED_CAST")
fun <T> get(clazz: Class<T>) = singletons[clazz] as T
}
// 初始化(Application)
class MyApp : Application() {
override fun onCreate() {
super.onCreate()
ServiceLocator.register(UserRepository::class.java, UserRepositoryImpl(UserApi()))
}
}
// 使用
class SomeClass {
private val repo: UserRepository = ServiceLocator.get(UserRepository::class.java)
}注意:Service Locator 会使依赖变隐式(难以在构造函数里看到),测试时需要替换全局状态,通常不推荐做为首选。
3) 使用 Dagger / Hilt(Google 推荐,适合中大型项目)
推荐理由:编译时注入、性能好、与 Android 生命周期集成(Hilt)——生产环境里最常用。下面给出 Hilt 的核心示例(Kotlin):
Gradle 依赖(简略):
gradle
// project build.gradle + app build.gradle 必要的 Hilt 依赖与 kapt
implementation "com.google.dagger:hilt-android:2.x"
kapt "com.google.dagger:hilt-android-compiler:2.x"
implementation "androidx.hilt:hilt-lifecycle-viewmodel:1.0.0" // 可选
kapt "androidx.hilt:hilt-compiler:1.0.0"代码示例:
kotlin
// Application
@HiltAndroidApp
class MyApplication : Application()
// 接口与实现
interface UserRepository { fun getUser(id: String): User }
class UserRepositoryImpl @Inject constructor(
private val api: UserApi
): UserRepository {
override fun getUser(id: String) = api.fetch(id)
}
// 提供外部依赖的 Module
@Module
@InstallIn(SingletonComponent::class) // Application 单例作用域
object NetworkModule {
@Provides
@Singleton
fun provideUserApi(): UserApi = UserApi()
@Provides
@Singleton
fun provideUserRepository(api: UserApi): UserRepository =
UserRepositoryImpl(api)
}
// ViewModel 注入
@HiltViewModel
class UserViewModel @Inject constructor(
private val repo: UserRepository
) : ViewModel()
// Activity 注入
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
private val vm: UserViewModel by viewModels()
}要点:
@HiltAndroidApp:启动 Hilt@Module+@Provides或@Binds提供依赖@InstallIn(...)指定作用域(SingletonComponent、ActivityComponent、ViewModelComponent 等)- 构造器注入(
@Inject constructor)是推荐方式 - Hilt 也支持
@Binds(将接口绑定到实现,避免创建实例)
优点:自动生成依赖图、生命周期/作用域管理、便于测试(可替换 module)、性能优秀。 缺点:学习曲线有点,配置繁琐(但 Hilt 大幅简化 Dagger 的样板)。
4) 轻量级容器:Koin(DSL 风格,运行时注入)
思想:用 Kotlin DSL 声明依赖,启动时加载 module,运行时解析。简单、直观,适合中小型项目或快速开发。
kotlin
// Gradle: implementation "io.insert-koin:koin-android:3.x"
// AppModule.kt
val appModule = module {
single { UserApi() } // 单例
single<UserRepository> { UserRepositoryImpl(get()) } // get() 注入 UserApi
viewModel { UserViewModel(get()) } // viewModel DSL
}
// Application
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
startKoin {
androidContext(this@MyApplication)
modules(appModule)
}
}
}
// Activity
class MainActivity : AppCompatActivity() {
private val vm: UserViewModel by viewModel()
}优点:学习门槛低、DSL 可读性好、快速迭代。 缺点:运行时解析代价、在大型复杂项目中不如 Dagger/Hilt 严格。
DIP 在代码中的具体体现(模式)
- 用接口(或抽象类)声明依赖:
interface UserRepository。高层(ViewModel/Presenter)只依赖接口。 - 在外部(Module/Factory/Application)把低层实现绑定到接口(
provideUserRepository/@Binds/ Koin 的single<UserRepository> { ... })。 - 通过构造器注入把接口注入到高层模块:
class UserViewModel(private val repo: UserRepository)。 这实现了“高层不依赖低层细节,细节依赖抽象”。
注入方式对比(优选顺序)
- 构造器注入(优先)—— 明确、易测试、DI 框架友好。
- 属性/字段注入(在某些框架下用,例如 Dagger/Hilt 对 Activity/Fragment)—— 可用,但隐藏依赖。
- 方法注入(较少见)—— 在需要延迟注入时有用。
- Service Locator(避免)—— 隐式依赖,不利于可测试性与可维护性。
测试友好策略
- 在设计接口后,编写 Fake/Mock 实现供测试注入(手动或由测试版 Module 覆盖 Hilt Module / Koin module)。
- Hilt 提供
@TestInstallIn能替换 Module 来注入假实现。Koin 可以在测试时 load different modules。
实战建议(什么时候用哪个)
- 小型 App / PoC / 学习:手动 DI 或 Koin(快速、简单)。
- 中大型 App / 团队项目 / 产品级别:Hilt(与 Android 生命周期集成、编译时检查、可扩展性强)。
- 注重性能 & 编译时安全:Dagger(或 Hilt,Hilt 是 Dagger 的封装)。
- 避免:直接在类里
new具体实现 —— 这违背 DIP/IoC。
简短 checklist (实战中要看)
- 是否用接口抽象关键依赖?(是 → OK)
- 是否在构造函数暴露依赖?(优先)
- 是否使用 DI 框架管理作用域(Activity/Fragment/ViewModel/Singleton)?
- 是否为测试提供替换实现?(通过 Module/ServiceLocator/test-only setup)