理解 Dagger 2 应用架构
understanding Dagger 2 app Architecture
我正在努力了解 Dagger 2 和依赖注入。我认为一个好的方法是看一下官方 Coffee example。我也通读了github页面上的官方文档,但我发现它对新手来说相当混乱。
正如您在下图中看到的,我剥离了所有 类 并为它们着色以了解发生了什么。但是我还是有些疑惑
我的问题:
1) 我听说我们使用 DI,因为在构造函数上传递依赖项会使我们的代码冗长。好吧,这个例子中的代码量和 类 大大超过了在构造函数中仅提供这两个参数所需要的量。此外,代码将更加人性化。这样做的好处是什么?
2) PumpModule 声明了一个提供者,它将它应该提供的东西作为参数……这有点违反直觉。那里到底发生了什么?
3)我真的迷路了(CoffeApp.java)
DaggerCoffeApp_Coffe.builder().build()
那是干什么的? DaggerCoffeApp_Coffe 在哪里? Android 工作室表示无法在任何地方找到它。
回答 1
"the amount of code and classes on this example enormously exceeds
what it would have taken to just supply these two parameters in a
constructor"
在示例代码或非常小的应用程序中 - 是的。在一个正常大小或大的应用程序中,如果不是数千次,你将有数百次提供 "these two parameters in a constructor".
DI 的最大优点之一是它允许(并且在某种程度上可能强制)您创建模块化应用程序,可以在其中开发模块并 测试隔离。这听起来可能没什么大不了的,但同样,当应用程序变得更大时,在不破坏东西的情况下添加新的更改变得非常困难。开发模块时,您可以通过定义提供所需功能的接口并为这些接口定义 @Inject
来将自己与应用程序的其余部分隔离开来。这样,如果您稍后(几个月,下一个版本?)决定需要 change/extend/rewrite 一些模块,只要您不更改它的接口,其他模块就不会受到影响。您将能够编写您的替换模块,然后在您的 @Provides
方法中只 'switch' 到它。
DI 的另一大优势是它允许您轻松地将模拟对象提供给单元测试。例如:假设您有一个 Activity
,它使用 GPS 位置提供程序来检测位置。如果你想在没有 DI 的情况下测试它,你必须在调试模式下 运行 你的应用程序在模拟器中,手动提供一些 "fake" 位置并在某些断点检查 activity 是否在预期状态。使用 DI,您可以轻松地将模拟位置提供程序提供给您的 Activity
,它使用您预定义的一些值模拟 GPS 位置更新。当然,您可以再次 运行 在模拟器(或真实设备)中手动 运行 您的应用程序,但您也可以 运行 它作为单元测试的一部分自动 运行 甚至在像 Jenkins 这样的持续集成服务器进程中。这样,每次更改代码时,您都可以 运行 测试并立即查看更改是否破坏了某些内容。另一个价值是自动测试可以节省 您的 时间。在示例中,您可能需要至少 2 分钟的时间来进行手动测试。自动测试将花费几秒钟,更重要的是它将 运行 不需要你的 attention/input 而 运行ning.
有关更多信息,我推荐 Jake Wharton 的这段精彩视频:
https://www.parleys.com/tutorial/5471cdd1e4b065ebcfa1d557/
这里是视频的幻灯片:
https://speakerdeck.com/jakewharton/dependency-injection-with-dagger-2-devoxx-2014
回答2
"PumpModule declares a provider that takes the thing it is supposed to
provide as a parameter"
该提供商提供 接口 ,而不是具体的 class。这就是重点。当您将应用程序迁移到 DI 时,您必须为每个要注入的 class 创建一个接口。正如答案 1 中所解释的那样,您将能够轻松地将具体实现替换为用于测试的模拟对象,或者为更高版本的应用程序替换为新的更好的实现。
例如:在某些时候,您决定需要 Rotary Vane Pump 而不是 Thermosiphon
。您编写 RotaryVanePump
class 然后只需将您的方法更改为 @Provides Pump providePump(RotaryVanePump pump) {
.
这是如何工作的((过度)简化解释):
- DI图由
DaggerCoffeApp_Coffe.builder().build()
构建(请先看答案3)
- 在某些时候 Dagger 在您的代码中发现
@Inject Pump mMyPump
;
- Dagger 发现您需要注入 Pump 并在 DI 图中寻找如何提供它。
- 它找到了
@Provides Pump providePump()
方法。 Dagger 发现它需要 RotaryVanePump
个对象。
- Dagger寻找DI图如何提供
RotaryVanePump
.
- 任何模块中都没有
provide
方法,但 RotaryVanePump
不需要,因为它具有无参数构造函数,因此 Dagger 可以实例化一个对象。
- 新对象被实例化为
RotaryVanePump
类型
- Dagger 在
providePump()
中将此对象作为实际参数提供。
providePump()
returns 将该对象作为 return 值。
RotaryVanePump
被注入到@Inject Pump mMyPump
字段。
而且这一切都是自动完成的,你不必关心它。
回答 3
DaggerCoffeApp_Coffe
由 Dagger 生成。您必须使用它才能 Android studio "see" 生成的 classes。
https://bitbucket.org/hvisser/android-apt
"What is that doing?
这就是整个魔术 :-)。它构建依赖关系图并在编译时检查是否满足所有依赖关系。 "at compile time" 将 Dagger 与所有其他无法在编译时验证图形的 DI 框架区分开来,如果您错过定义某些依赖项,您将得到一个丑陋的 运行 时间错误。
为了让您的所有 @Inject
正常工作,您必须首先使用这样的调用构建图表。
我正在努力了解 Dagger 2 和依赖注入。我认为一个好的方法是看一下官方 Coffee example。我也通读了github页面上的官方文档,但我发现它对新手来说相当混乱。
正如您在下图中看到的,我剥离了所有 类 并为它们着色以了解发生了什么。但是我还是有些疑惑
我的问题:
1) 我听说我们使用 DI,因为在构造函数上传递依赖项会使我们的代码冗长。好吧,这个例子中的代码量和 类 大大超过了在构造函数中仅提供这两个参数所需要的量。此外,代码将更加人性化。这样做的好处是什么?
2) PumpModule 声明了一个提供者,它将它应该提供的东西作为参数……这有点违反直觉。那里到底发生了什么?
3)我真的迷路了(CoffeApp.java)
DaggerCoffeApp_Coffe.builder().build()
那是干什么的? DaggerCoffeApp_Coffe 在哪里? Android 工作室表示无法在任何地方找到它。
回答 1
"the amount of code and classes on this example enormously exceeds what it would have taken to just supply these two parameters in a constructor"
在示例代码或非常小的应用程序中 - 是的。在一个正常大小或大的应用程序中,如果不是数千次,你将有数百次提供 "these two parameters in a constructor".
DI 的最大优点之一是它允许(并且在某种程度上可能强制)您创建模块化应用程序,可以在其中开发模块并 测试隔离。这听起来可能没什么大不了的,但同样,当应用程序变得更大时,在不破坏东西的情况下添加新的更改变得非常困难。开发模块时,您可以通过定义提供所需功能的接口并为这些接口定义 @Inject
来将自己与应用程序的其余部分隔离开来。这样,如果您稍后(几个月,下一个版本?)决定需要 change/extend/rewrite 一些模块,只要您不更改它的接口,其他模块就不会受到影响。您将能够编写您的替换模块,然后在您的 @Provides
方法中只 'switch' 到它。
DI 的另一大优势是它允许您轻松地将模拟对象提供给单元测试。例如:假设您有一个 Activity
,它使用 GPS 位置提供程序来检测位置。如果你想在没有 DI 的情况下测试它,你必须在调试模式下 运行 你的应用程序在模拟器中,手动提供一些 "fake" 位置并在某些断点检查 activity 是否在预期状态。使用 DI,您可以轻松地将模拟位置提供程序提供给您的 Activity
,它使用您预定义的一些值模拟 GPS 位置更新。当然,您可以再次 运行 在模拟器(或真实设备)中手动 运行 您的应用程序,但您也可以 运行 它作为单元测试的一部分自动 运行 甚至在像 Jenkins 这样的持续集成服务器进程中。这样,每次更改代码时,您都可以 运行 测试并立即查看更改是否破坏了某些内容。另一个价值是自动测试可以节省 您的 时间。在示例中,您可能需要至少 2 分钟的时间来进行手动测试。自动测试将花费几秒钟,更重要的是它将 运行 不需要你的 attention/input 而 运行ning.
有关更多信息,我推荐 Jake Wharton 的这段精彩视频: https://www.parleys.com/tutorial/5471cdd1e4b065ebcfa1d557/
这里是视频的幻灯片: https://speakerdeck.com/jakewharton/dependency-injection-with-dagger-2-devoxx-2014
回答2
"PumpModule declares a provider that takes the thing it is supposed to provide as a parameter"
该提供商提供 接口 ,而不是具体的 class。这就是重点。当您将应用程序迁移到 DI 时,您必须为每个要注入的 class 创建一个接口。正如答案 1 中所解释的那样,您将能够轻松地将具体实现替换为用于测试的模拟对象,或者为更高版本的应用程序替换为新的更好的实现。
例如:在某些时候,您决定需要 Rotary Vane Pump 而不是 Thermosiphon
。您编写 RotaryVanePump
class 然后只需将您的方法更改为 @Provides Pump providePump(RotaryVanePump pump) {
.
这是如何工作的((过度)简化解释):
- DI图由
DaggerCoffeApp_Coffe.builder().build()
构建(请先看答案3) - 在某些时候 Dagger 在您的代码中发现
@Inject Pump mMyPump
; - Dagger 发现您需要注入 Pump 并在 DI 图中寻找如何提供它。
- 它找到了
@Provides Pump providePump()
方法。 Dagger 发现它需要RotaryVanePump
个对象。 - Dagger寻找DI图如何提供
RotaryVanePump
. - 任何模块中都没有
provide
方法,但RotaryVanePump
不需要,因为它具有无参数构造函数,因此 Dagger 可以实例化一个对象。 - 新对象被实例化为
RotaryVanePump
类型
- Dagger 在
providePump()
中将此对象作为实际参数提供。 providePump()
returns 将该对象作为 return 值。RotaryVanePump
被注入到@Inject Pump mMyPump
字段。
而且这一切都是自动完成的,你不必关心它。
回答 3
DaggerCoffeApp_Coffe
由 Dagger 生成。您必须使用它才能 Android studio "see" 生成的 classes。
https://bitbucket.org/hvisser/android-apt
"What is that doing?
这就是整个魔术 :-)。它构建依赖关系图并在编译时检查是否满足所有依赖关系。 "at compile time" 将 Dagger 与所有其他无法在编译时验证图形的 DI 框架区分开来,如果您错过定义某些依赖项,您将得到一个丑陋的 运行 时间错误。
为了让您的所有 @Inject
正常工作,您必须首先使用这样的调用构建图表。