在这个新系列中, 我将解释什么是依赖注入, 它的主要目的是什么, 以及在 Android 工程中如何使用 Dagger 函数库 http://square.github.io/dagger/ 实现它, Dagger 是目前最流行的专为 Android 设计的依赖注入函数库.
本文是之前的文章Android 中 MVP 的实现 http://antonioleiva.com/mvp-android/ 的后续之作, 因为我相信读者中有一部分人会很乐意看到这两个特性在同一个工程中实现, 而且我认为它们可以很好的协同工作.
本文将仅介绍基本的理论知识来奠定基础. 理解依赖注入的概念以及它存在的理由是很重要的, 否者我们会认为得到的好处不如付出的努力.
什么是依赖注入
如果我们想要注入依赖, 首先要理解依赖是什么. 简单的说, 依赖是我们代码中两个模块之间的耦合 (在面向对象语言中, 指的是两个类), 通常是其中一个模块使用另外一个提供的功能.
为什么依赖是危险的?
从上层到底层依赖都是危险的, 因为我们在某种程度上把两个模块进行耦合, 这样当需要修改其中一个模块时, 我们必须修改与其耦合的模块的代码. 这对于创建一个可测试的 app 来说是很不利的, 因为单元测试要求测试一个模块时, 要保证它和 app 中其他模块是隔离的. 为了做到这一点, 我们需要使用 mocks 代替依赖, 想象一下这样的代码:
- public class Module1{
- private Module2 module2;
- public Module1(){
- module2 = new Module2();
- }
- public void doSomething(){
- ... module2.doSomethingElse();
- ... }
- }
如何在不测试 doSomethingElse 函数的前提下测试 doSomething 函数呢? 如果测试失败, 是哪个函数导致的呢? 我们不得而知. 如果 doSomethingElse 函数在数据库中保存数据或者向服务器端发起 API 请求, 那么事情将变得更加糟糕.
每当敲下 new 关键字我们都应该意识到这可能是需要避免的强依赖. 编写更少的模块当然不是解决方案, 不要忘记单一职责原则. http://en.wikipedia.org/wiki/Single_responsibility_principle
如何解决呢? 依赖反转
如果不能在一个模块内部初始化另外的模块, 那么需要以其他的形式初始化这些模块. 你能想象如何实现吗? 没错, 通过构造函数. 这基本上就是依赖反转原则 http://en.wikipedia.org/wiki/Dependency_inversion_principle 的涵义了. 你不应该依赖具体的模块对象, 应该依赖抽象.
前面的代码应该修改为:
- public class Module1{
- private Module2 module2;
- public Module1(Module2 module2){
- this.module2 = module2
- }
- public void doSomething(){
- ... module2.doSomethingElse();
- ... }
- }
那么什么是依赖注入呢?
你已经知道了! 它通过构造函数传递依赖 (注入), 从而把创建模块的任务从另一个模块内部抽离出来. 对象在其他地方创建, 并以构造函数参数的形式传递给另一个对象.
但新问题出现了. 如果我们不能在模块内部创建其他的模块, 那么必须有个地方对这些模块进行初始化. 另外, 如果我们需要创建的模块的构造函数包含大量的依赖参数, 代码将变得丑陋和难以阅读, app 中将存在大量传递的对象. 依赖注入正是为解决这类问题而诞生的.
什么是依赖注入器?
我们可以把它理解成 app 中的另一个模块, 专门负责提供其他模块的实例并注入他们的依赖. 这是它的基本义务. 模块的创建集中于 app 中的一个统一的入口, 我们对它有完全的控制权.
最后, 什么是 Dagger?
Dagger http://square.github.io/dagger/ 是专为低端设备设计的依赖注入器. 大部分依赖注入器通过反射来创建和注入依赖. 反射机制是很棒的, 但在低端设备上面耗时严重, 特别是在老的 android 版本上面. 然而, Dagger 使用预编译器创建工作所需的所有类. 这样一来, 就不需要用到反射了. Dagger 相比其他依赖注入器功能稍弱, 但却是效率最高的.
Dagger 只是用于测试吗?
当然不是! 它使得在其他 app 中重用你的模块变得容易, 或者在相同的 app 中改变这些模块. 想象这样一个例子: 你的 app 在 debug 模式下需要从本地文件中读取数据, 而在 release 模式下需要从服务器端 API 请求中获取这些数据. 完全有可能通过在不同的模式下注入不同的模块来实现.
结论
我知道这篇文章有点难度, 但我认为在继续下一篇文章之前, 有必要把基本概念先明确好. 我们已经知道什么是依赖, 通过依赖反转改进了什么以及我们如何通过依赖注入器实现它.
在下一篇文章中我们会开始真正的实践操作, 敬请关注!
来源: http://www.jianshu.com/p/d9c592a62744