Kotlin 38. Dependency Injection依赖注入以及Hilt在Kotlin中的使用,系列1:依赖注入介绍

一起来学Kotlin:概念:25. Dependency Injection依赖注入以及Hilt在Kotlin中的使用,系列1:依赖注入介绍

此系列博客中,我们将主要介绍:

  • Dependency Injection(依赖注入) 概念介绍。网上看了许多关于 DI 的介绍,云里雾里。这里,我们通过通俗易懂地方式对其进行介绍。
  • 手动依赖注入介绍。为了让大家更容易理解 Hilt,我们先介绍如何通过手动的方式实现依赖注入效果。
  • Hilt 注释(annotations)介绍及使用案例
  • MVVM 案例中如何使用 Hilt

此博客主要介绍Dependency Injection(依赖注入)概念。



1 什么是 DI

所以在开始理解 DI 之前,首先让我们了解编程中的依赖(Dependency)是什么意思。

当 A 类使用 B 类的某些功能时,则表示 A 类具有 B 类的依赖项。在 Java 中,在我们使用其他类的方法之前,我们首先需要创建该类的对象(即类 A 需要创建类 B 的实例)。

简单来说,依赖注入就是一个对象A需要(依赖于)另外一个对象B的实例。 需要把对象B的实例注入到对象A中,而这个“注入”的动作,要么由程序员手动执行,要么交给软件框架执行。

因此,将创建对象的任务转移给其他人并直接使用依赖称为依赖注入(DI)。

这里再举个例子。

假设我们有一个汽车类,其中包含各种对象,例如车轮、引擎等。在不用依赖注入时,我们只能在汽车(类)里面 new 一个引擎(实例)出来:private val engine = Engine():

class Car {<!-- -->
    private val engine = Engine()

    fun start() {<!-- -->
        engine.start()
    }
}

fun main(args: Array) {<!-- -->
    val car = Car()
    car.start()
}

这样写有一个问题:汽车 Car 类和引擎 Engine 严重耦合。每个 Car 类的实例都使用不同的 Engine 实例。这种汽车 Car 类内部 “建造引擎” 的方式,使得该汽车 Car 类难以测试及维护。如果我们需要把汽车区分为内燃机汽车和电动汽车时,只能去 Car 类的内部修改。这就违反了面向对象软件开发的“开放封闭原则”。

那么 DI风格的代码应该是什么样的呢? Car类在它的构造函数里接收一个 Engine 类型的参数:class Car(private val engine: Engine){ ... }

class Car(private val engine: Engine) {<!-- -->
    fun start() {<!-- -->
        engine.start()
    }
}

fun main(args: Array) {<!-- -->
    val engine = Engine()
    val car = Car(engine)
    car.start()
}

这样汽车 Car 类就不需要自己内部“建造引擎”,它需要什么样的引擎,直接在实例化的时候从外部传(注入)进来就可以了。

在这里,我们就可以说 Car 类的实例化依赖于Engine类的实例,我们在构造 Car 类实例 car(实例对象变量一般首字母小写)的时候传入(注入)了 Engine 类的实例。

这么做的好处也显而易见:

  • Car类可重复使用,你需要一辆燃油车,在实例化的时候传入内燃机引擎;而当需要一辆电动车的时候,传入电动机引擎;
  • Car类的可测试性变成了现实(你可以传入不同类型的FakeEngine做测试 Test Double)。

2 DI 的类型

基本上有三种 DI 类型:

  • constructor injection(类的构造函数注入):依赖项是通过类构造函数提供的。
  • setter injection(类字段注入):客户端公开一个 setter 方法,注入器使用它来注入依赖项。
  • interface injection:依赖项提供了一个注入器方法,可以将依赖项注入传递给它的任何客户端。 客户端必须实现一个接口,该接口公开一个接受依赖项的 setter 方法。

上面的例子就是 constructor injection(类的构造函数注入)。第二种:setter injection(类字段注入),比如下代码片段所示中的 lateinit var engine: Engine

class Car {<!-- -->
    lateinit var engine: Engine

    fun start() {<!-- -->
        engine.start()
    }
}

fun main(args: Array) {<!-- -->
    val car = Car()
    car.engine = Engine()
    car.start()
}

所以现在 DI 的责任是:

  • 创建对象
  • 知道哪些类需要那些对象
  • 并为他们提供所有这些对象

如果对象有任何变化,那么 DI 会调查它,它不应该关注使用这些对象的类。 这样,如果将来对象发生变化,则其 DI 有责任向类提供适当的对象。

顺便说一句,我们之前有一个博客,介绍的是 SOLID 原则(面向对象编程和设计的五个基本原则)。它里面的第五条,类应该依赖于抽象而不是具体化(简单来说,硬编码)。根据这些原则,一个类应该专注于履行其职责,而不是创建履行这些职责所需的对象。 这就是 DI 发挥作用的地方:它为类提供所需的对象。

3 DI 的优劣

DI 的优点:

  • 有助于单元测试。
  • 样板代码减少了,因为依赖项的初始化由注入器组件完成。
  • 扩展应用程序变得更加容易。
  • 有助于实现松散耦合,这在应用程序编程中很重要。

DI 的缺点:

  • 学习起来有点复杂,如果过度使用会导致管理问题和其他问题。
  • 许多编译时(compile time)错误被推送到运行时(run-time)。
  • DI 框架是通过反射或动态编程实现的。 这可能会阻碍 IDE 自动化的使用,例如“查找引用”、“显示调用层次结构”和安全重构。

实现 DI 的库和框架:

  • Spring (Java)
  • Google Guice (Java)
  • Dagger (Java and Android)
  • Castle Windsor (.NET)
  • Unity(.NET)

通过以上的分析,我们对依赖注入(DI)有了一个相对直观的印象。依赖注入(DI)的目的就是为了代码间的解耦,解耦的目的是为了提高代码的可重用性和可测试性,而提高了代码的可重用性和可测试性是为了增强代码的鲁棒性(Robust)。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Kotlin 依赖注入是一种设计模式,它可以帮助我们解耦代码并提高可测试性。Kotlin 常用的依赖注入框架有 Koin 和 Dagger2。 Koin 是一个轻量级的依赖注入框架,它使用 Kotlin 语言特性来实现依赖注入。Koin 简单易用,不需要编写任何代码,只需要定义模块和依赖关系即可。以下是一个使用 Koin 进行依赖注入的示例: ```kotlin // 定义一个模块 val appModule = module { single { NetworkService() } factory { UserRepository(get()) } viewModel { MainViewModel(get()) } } // 在 Application 初始化 Koin class MyApp : Application() { override fun onCreate() { super.onCreate() startKoin { androidContext(this@MyApp) modules(appModule) } } } // 在 Activity 使用依赖注入 class MainActivity : AppCompatActivity() { private val viewModel: MainViewModel by viewModel() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) // 使用 viewModel viewModel.doSomething() } } ``` Dagger2 是一个功能强大的依赖注入框架,它可以在编译时生成依赖注入代码,提高运行时的性能。Dagger2 的使用比 Koin 更为复杂,需要定义组件、模块、依赖关系等。以下是一个使用 Dagger2 进行依赖注入的示例: ```kotlin // 定义一个模块 @Module class AppModule { @Provides fun provideNetworkService(): NetworkService { return NetworkService() } @Provides fun provideUserRepository(networkService: NetworkService): UserRepository { return UserRepository(networkService) } } // 定义一个组件 @Component(modules = [AppModule::class]) interface AppComponent { fun inject(activity: MainActivity) } // 在 Application 初始化 Dagger2 class MyApp : Application() { lateinit var appComponent: AppComponent override fun onCreate() { super.onCreate() appComponent = DaggerAppComponent.builder() .build() } } // 在 Activity 使用依赖注入 class MainActivity : AppCompatActivity() { @Inject lateinit var viewModel: MainViewModel override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) // 注入依赖 (application as MyApp).appComponent.inject(this) // 使用 viewModel viewModel.doSomething() } } ``` 总体来说,Koin 适合小型项目和快速原型开发,而 Dagger2 则适合大型项目和需要高性能的场景。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

破浪会有时

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值