Wonder4 Life

译 Dependency injection on Android: Dagger(Part 1)

On this new series I will explain what dependency injection is, what its main purpose is and how to use it on an Android project by using Dagger, the best know implementation designed with Android in mind.

It will be a follow-up from my previous post about MVP for Android, because I know some of you are quite interested in see them implemented in the same project, and I think they work quite well together.

This first part is going to be only a little theory to settle the basis. It’s important to understand what it is and why it exists, because if not we will think the benefits are not enough for the effort.

在这个新的系列文章中我将解释什么是依赖注入,它的主要目的是什么以及如何在 Android 项目中使用 Dagger,一个最著名的 Android 版本的实现。

本文是 MVP for Android 的后续文章,因为我知道你们中有一部分人对在项目中整合它们很感兴趣,而且我认为它们可以很好的协作。

第一部分将仅介绍一些理论来奠定基础。理解它是什么以及为什么存在是很重要的,因为如果我们不认为它有足够多的益处,也不会有足够的动力去使用它。

什么是依赖?(What is a dependency?)

If we want to inject dependencies, we first need to know what a dependency is. In short, a dependency is a coupling between two modules of our code (in oriented object languages, two classes), usually because one of them uses the other to do something.

如果我们想要注入依赖,我们首先需要知道什么是依赖。依赖是代码中两个模块间的耦合(在面向对象语言中,就是两个类),通常源于一个模块使用另外一个模块做些事。

为何依赖是危险的?(Why dependencies are dangerous?)

Dependencies from high to low level are dangerous because we couple both modules in a way that if we need to change one module with another,we necessarily need to modify the code of the coupled module. That’s really bad if we want to create a testable app, because unit testing requires that when we are testing a module, it is isolated from the rest of modules in our app. To do this, we need to substitute dependencies with mocks. Imagine a code like this:

从上层到底层的依赖是危险的,因为我们耦合了两个模块,如果我们需要修改其中一个模块时,我们需要修改耦合的模块的代码。如果我们想创建可测试的应用这将非常糟糕,因为在我们测试一个模块时,单元测试需要隔离其他的模块。我们需要使用 mocks 替换依赖的模块,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Module1{
private Module2 module2;
public Module1(){
module2 = new Module2();
}
public void doSomething(){
...
module2.doSomethingElse();
...
}
}

How do we test ‘doSomething’ without testing ‘doSomethingElse’? If test fails, what method is the one that is failing? We can’t know. And things get worse if this ‘doSomethingElse’ method saves something in database or performs an API call.

Every ‘new’ we write is a hard dependency we probably need to avoid. And no, writing less modules isn’t a solution, don’t forget single responsibility principle.

我们如何在不测试 doSomethingElse 的情况下测试 doSomething 呢?如果测试失败,哪个函数导致的失败呢?我们不得而知。如果 doSomethingElse 函数保存数据到数据库或者发起 API 请求,事情将变得更糟。

每个我们写下的 ‘new’ 都是一个强依赖,我们需要尽量避免。并且通过减少模块数量并不是最终解决方案,不要忘记单一职责原则

如何解决它?依赖反转 (How to solve it? Dependency inversion)

If we can’t instantiate modules inside another module, we need to provide those modules in another way. Can you imagine how? Exactly, via constructor. That is basically what dependency inversion principle means. You shouldn’t rely on concrete module objects, only on abstractions.

Our previous example code would be something like this:

如果我们不应该在另外的模块内部初始化模块,那么需要以其他形式提供这些模块。你能想象如何实现吗?没错,通过构造函数。这就是依赖反转原则。你不应该依赖具体的模块对象,应该仅依赖抽象。

前面的示例代码应该修改为:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Module1{
private Module2 module2;
public Module1(Module2 module2){
this.module2 = module2
}
public void doSomething(){
...
module2.doSomethingElse();
...
}
}

什么是依赖注入?(So what’s dependency injection?)

You already know! It consists of passing dependencies (inject them) via constructor in order to extract the task of creating modules out from other modules. Objects are instantiated somewhere else and passed as constructor attributes when creating the current object.

But here it comes a new problem. If we can’t create modules inside modules, there must be a place where those modules are instantiated. Besides, if we need to create modules with huge constructors including lots of dependencies, code will become dirty and hard to read, with objects travelling around the inmensity of our app. That’s what a dependency injector solves.

你已经知道了!通过构造函数传递依赖(注入它们),从而把在一个模块中创建另一个模块的任务提取出来。需要的对象在其他地方初始化,在当前对象创建时,以构造函数参数的形式传递进来。

但带来了新的问题。如果我们不能在模块内部创建其他的模块,那么必须有个地方对这些模块进行初始化。除此之外,如果我们创建的模块带有庞大的构造函数包含了大量的依赖模块,代码将不够美观和难以阅读,对象传递将遍布整个应用。这正是依赖注入要解决的问题。

什么是依赖注入器?(What is a dependency injector?)

We can consider it as another module in our app that is in charge of providing instances of the rest of modules and inject their dependencies. That is basically its duty. The creation of modules is localized in a single point in our app, and we have full control over it.

我们可以认为它是应用中的另一个模块,负责提供其他模块的实例并注入他们的依赖。基本上这是它的职责。模块的创建位于应用的单独位置,我们对其拥有完全的控制。

最后…什么是 Dagger?(And finally… What is Dagger?)

Dagger is a dependency injector designed for low-end devices. Most dependency injectors rely on reflection to create and inject dependencies. Reflection is awesome, but is very time consuming on low-end devices, and specially on old android versions. Dagger, however, uses a pre-compiler that creates all the classes it needs to work. That way, no reflection is needed. Dagger is less powerful than other dependency injectors, but it’s the most efficient.

Dagger为低端设备设计的依赖注入器。大部分依赖注入器依靠反射机制来创建和注入依赖。反射机制是极好的,但在低端设备上非常消耗时间,尤其是过去的 android 版本。Dagger 使用预编译器创建需要的所有类。无需采用反射。Dagger 相比其他依赖注入器不那么强大,但是它是最有效率的

Dagger 仅用于测试吗?(Is Dagger only for testing?)

Of course not! It makes easier to reuse your modules in other apps, or even change them in the same app. Think for example in an app which takes its data from some local files on debug and from an API service on release. That’s perfectly possible by injecting one module or another on each case.

当然不!它使得在其他应用中重用你的模块更容易,甚至在同一应用中改变这些模块。例如一个应用在 debug 模式下从本地文件中读取数据,而在 release 模式下从服务器 API 请求数据。针对不同的模式,可以注入不同的模块来实现。

结论 (Conclusion)

I know this post it’s a bit hard, but I think it’s very important to establish the terms we are going to deal with on next episodes. We already know what dependencies are, what we improve with dependency inversion and how we can implement it by using a dependency injector.

On next episode we will get our hands dirty. so stay tuned!

我知道这篇文章有点难,但我认为建立理论基础用于理解下一篇文章非常重要。我们已经知道什么是依赖,通过依赖反转改进了什么以及我们如何通过依赖注入器实现它。

下一篇文章中我们将开始动手去做。敬请期待!

Reference

Dependency injection on Android: Dagger (Part 1)