[译] 依赖注入在多模块工程中的应用

Plaid 应用中引入一个 DI 框架过程当中咱们学到的东西

插图来自 [Virginia Poltrack](https://twitter.com/vpoltrack)

总的来讲,这不是一篇关于依赖注入的文章,也不是关于咱们为何选择库 X 而不是库 Y 的文章。 相反的,本文从依赖注入的角度介绍了咱们对 Plaid 进行模块化实践的主要成果。前端

咱们的设置

在前面的文章中,我写过 Plaid 应用模块化的总体过程。 一款拼接应用 Plaid — 总体到模块化: 模块化 Plaid 应用的初衷、过程和结果java

让我以鸟瞰图的形式快速回顾一下 Plaid 的样子。android

咱们有一个包含主启动 activity 的 app 模块,同时也有一些依赖 app 模块的动态功能模块(DFM)。每个 DFM 都包含至少一个与所讨论功能相关的 activity、代码和资源。ios

app 模块依赖一个包含了共享的代码和资源以及第三方库的 core 模块。git

Plaid 的模块依赖图

在咱们开始模块化操做和以 Dagger 为主介绍依赖注入以前,先来熟悉下 Plaid 的相关类和函数:github

class DesignerNewsInjector {

    fun providesApi(...): DesignerNewsService { ... }

}
复制代码

虽然这是一个很是好的解决方案,但咱们仍是手工编写了大量的样板代码。后端

在任何须要注入的地方,咱们都须要在合适的时机调用底层函数,大多数状况下不是在对象初始化时就是在 onCreate 方法中。bash

依赖注入的简要介绍

依赖注入基本上意味着你不用在你须要的地方建立它们,而是在别的地方建立。而后这些对象的引用能够被传递到须要使用它们的类中。markdown

这点能够经过本身编写或者集成某个依赖注入库来实现,咱们选择了集成 Dagger 2。多亏了 Dagger,为了获取一个可使用的已初始化的 service,咱们全部要作的就是以下内容:app

@Inject lateinit var service: DesignerNewsService
复制代码

全部对 service 的依赖能够变成 provides 函数的传参。咱们为依赖注入需求选择了 Dagger 意味着咱们的依赖图在编译阶段会被建立。下面的章节中要记住这一点。

咱们在 Plaid 应用中集成 Dagger 的方式

当咱们决定引入 Dagger 到 Plaid 应用时,咱们已经学到了宝贵的一课,尤为是对模块化。

不要试图一次就覆盖太多内容。

这意味着花一些时间研究清楚实现一个新功能的最小必要范围是有意义的。咱们接下来要讨论的 MVP,即在团队内部审视咱们是否在向着正确的方向前进。坚持这种作法能够防止咱们进行太大而没法高效利用的变动。这也容许咱们在整个代码库中逐步推出更改,与此同时每一个人的任务也可持续进行。

在 Plaid 应用内咱们使用已验证后的 about 功能模块做为 Dagger 的练习模块。这里咱们能够添加 Dagger 而不会干扰到其余模块或负载。你能够在这里查看初始提交

依赖图解

当为一个单块应用引入依赖注入库时,一般整个应用有个单一的依赖图。

单块项目中的经典简化依赖图

这可使组件间共享依赖。在一些库中,依赖能够被设置做用域来避免冲突,或者为被注入对象提供一种特殊的实现。

模块化的怪异之处

对一个模块化的应用,尤为是使用动态功能模块的应用这却不起做用。让咱们仔细地研究下应用和动态功能模块如何彼此依赖。一个动态功能模块知道 application 模块的存在。application 模块大体知道动态功能模块的存在,可是不能直接执行该模块的代码。对于依赖注入,这意味着总体图必须被分解成片。

对一个模块化应用,简单的依赖图一般大体长成下面这样。

模块具备清晰的边界而且被封装在一个 DFM 依赖图中

更具体的是,Plaid 中组件规划图看起来像这样。

Plaid 的组件规划图

每一个 DFM 都有它本身的组件,以组件所在的功能模块命名。app 模块中的 HomeComponent 组件就是如此。

还有一个包含共享依赖项的组件,它位于 core 库中并被称做 CoreComponentCoreComponent 背后的主要思想是提供可被整个应用使用的对象。它结合了一些 Dagger 模块,这些模块位于 core 库并能够在整个应用中复用。

此外,因为依赖图具备方向性,所以只能经过如下方式共享 Dagger 组件: DFM 图能够从 application 模块来访问 Dagger 组件。application 模块能够从它依赖的库中访问组件,但方向反过来则不行。

跨模块边界共享组件

为了共享 Dagger 组件,它们须要被整个应用访问到。在 Plaid 中咱们决定使用 Application 类来让咱们的 CoreComponent 变得可访问。

class PlaidApplication : Application() {

  private val coreComponent: CoreComponent by lazy {
    DaggerCoreComponent
      .builder()
      .markdownModule(MarkdownModule(resources.displayMetrics))
      .build()
  }

  companion object {

    @JvmStatic fun coreComponent(context: Context) =
      (context.applicationContext as PlaidApplication).coreComponent
  }
}
复制代码

被实例化的 CoreComponent 组件如今能够从应用中任何具备 context 的地方来访问,经过调用 PlaidApplication.coreComponent(context) 的方式。

使用一个扩展函数可使 this 更好地访问:

fun Activity.coreComponent() = PlaidApplication.coreComponent(this)
复制代码

组件中的组件

为了把 CoreComponent 包含到另外一个组件中,有必要在组件建立时提供它。让咱们看一下在 SearchComponent` 中是如何作到的:

@Component(modules = [...], dependencies = [CoreComponent::class])
interface SearchComponent {

  @Component.Builder
  interface Builder {

    fun coreComponent(coreComponent: CoreComponent): Builder
    // modules
  }
}
复制代码

在生成的 DaggerSearchComponent 作初始化时咱们像这样设置了 CoreComponent

DaggerSearchComponent.builder()
  .coreComponent(activity.coreComponent())
  // modules
  .build()
.inject(activity)
复制代码

这里的技巧是把 CoreComponent 设置为 SearchComponent 的一个依赖:

@Component(
    modules = [SearchModule::class],
    dependencies = [CoreComponent::class]
)
interface SearchComponent : BaseActivityComponent<SearchActivity>
复制代码

CoreComponentSearchComponent 的一个依赖。当 CoreComponent 像上面那样被引用为 SearchComponent 的一个组件依赖时,全部的 CoreComponent 方法能够在 SearchComponent 中使用,或者在其余 Dagger 组件中使用,就好像他们变成注解 @Provides 标记的方法。

组件依赖与它们各自为 SearchActivity 提供实现方法的模块(绿色)

这样作的的一个好处是:在功能图中无需重复 @Modules ,却能够经过 CoreComponent 或其余与之绑定的模块来透明地提供出去。

例如,CoreDataModule 绑定在 CoreComponent 中,并提供 Retrofit 等。Retrofit 实例如今能够被任何与 CoreComponent 合并的组件访问到。

下一步要作什么

读完这篇文章,你能够看到模块化你的应用须要把依赖注入考虑进去。引入的功能模块边界经过分离的依赖图反映在依赖注入中。意识到这个限制可有助于为共享组件找到合适的位置。

你能够深刻到代码中来查看咱们如何使用 Dagger 解决 Plaid 中的依赖注入问题。

[CoreComponent](https://github.com/nickbutcher/plaid/blob/master/core/src/main/java/io/plaidapp/core/dagger/CoreComponent.kt) 是一个好的阅读开端,[AboutComponent](https://github.com/nickbutcher/plaid/blob/master/about/src/main/java/io/plaidapp/about/dagger/AboutComponent.kt) 也是,由于它没有太多的外部依赖。

若是发现译文存在错误或其余须要改进的地方,欢迎到 掘金翻译计划 对译文进行修改并 PR,也可得到相应奖励积分。文章开头的 本文永久连接 即为本文在 GitHub 上的 MarkDown 连接。


掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 AndroidiOS前端后端区块链产品设计人工智能等领域,想要查看更多优质译文请持续关注 掘金翻译计划官方微博知乎专栏

相关文章
相关标签/搜索