Dagger2-依赖注入

介绍依赖注入的相关知识

依赖注入 Dependency injection

依赖注入,就是构建对象并在我们需要时把他们传入。
想象有一个简单的类,UserManager, 他依赖UserStoreApiService。如果没有依赖注入,这个类看起来会是这个样子:
UserStoreApiServie两者都是在UserManager中创建和提供的:
示意图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class UserManager {
private ApiService apiService;
private UserStore userStore;
//No-args constructor. Dependencies are created inside.
public UserManager() {
this.apiService = new ApiSerivce();
this.userStore = new UserStore();
}
void registerUser() {/* */}
}
class RegisterActivity extends Activity {
private UserManager userManager;
@Override
protected void onCreate(Bundle b) {
super.onCreate(b);
this.userManager = new UserManager();
}
public void onRegisterClick(View v) {
userManager.registerUser();
}
}

上面的代码会出现一些问题,想象一下,我们需要改变UserStore的实现,用SharedPreferences来作为他的存储机制,他需要至少一个Context对象来创建实例,所以我们需要通过UserStore的构造函数传入。他意味着,UserManager类中也需要被修改来使用新的UserStore构造器。如果有很多类使用了UserStore,他们需要全部被修改。

但是如果用了依赖注入:
他的依赖类是在外面创建和提供的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class UserManager {
private ApiService apiService;
private UserStore userStore;
//Dependencies are passed as arguments
public UserManager(ApiService apiService, UserStore userStore) {
this.apiService = apiService;
this.userStore = userStore;
}
void registerUser() {/* */}
}
class RegisterActivity extends Activity {
private UserManager userManager;
@Override
protected void onCreate(Bundle b) {
super.onCreate(b);
ApiService api = ApiService.getInstance();
UserStore store = UserStore.getInstance();
this.userManager = new UserManager(api, store);
}
public void onRegisterClick(View v) {
userManager.registerUser();
}
}

在相似的情况下,我们改变UserStore或者ApiService两个依赖的实现方式,我们不需要修改UserManager的代码。两个依赖的对象都是通过外面提供的。

所以使用依赖注入的优势是什么???

优势

构造/使用 的分离

当我们构造类实例,通常这些对象会在其他地方使用到,多亏这个方法让我们的代码更加模块化,所有依赖都可以很简单的被替换掉,并且不会与我们应用的逻辑产生冲突,想要改变DataBaseUserStoreSharedPrefesUserStore?好的,我们只需要关心公开的API(与DataBaseUserStore相同的)或者实现相同的接口。

单元测试

真正的单元测试假设一个类完全可以被隔离进行测试的,不需要了解他的相关依赖。在实践中,基于我们的UserManager类编写一个单元测试类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class UserManagerTests {
UserManager userManager;
@Mock
ApiService apiServiceMock;
@Mock
UserStore userStoreMock;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
userManager = new UserManager(apiServiceMock, userStoreMock);
}
@After
public void tearDown() {
}
@Test
public void testSomething() {
//Test our userManager here - all its dependencies are satisfied
}
}

它只能使用DI,多亏UserManger是完全独立于UserStoreApiService实现的。我们可以提供这些类的Mock,(简单的说Mock是一些拥有相同API的类,它在方法中不做任何事情,并且返回我们期望的值),然后在真实的依赖的实现对UserManager测试隔离。

独立/并行开发

因为模块化的代码设计(UserStore 的实现可以完全独立与UserManager之外)。这样让程序间的代码更容易分离,(其实就是解耦)。只有UserStore的接口能被其他类调用,(尤其是被UserManager调用的UserStore的public方法)。其余的实现和逻辑可以用单元测试来做测试。

依赖注入的框架

除了上面的优势之外,依赖注入模式也有一些弊端。一个就是会产生更多的模块代码。想象一个简单的LoginActivity类,他在MVP模式中被实现。这个类看起来就像这样。
唯一有问题的部分代码就是LoginPresenter的初始化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class LoginActivity extends AppCompatActivity {
LoginActivityPresenter presenter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//这边需要把需要依赖的类全部手动先初始化一遍
OkHttpClient okHttpClient = new OkHttpClient();
RestAdapter.Builder builder = new RestAdapter.Builder();
builder.setClient(new OkClient(okHttpClient));
RestAdapter restAdapter = builder.build();
ApiService apiService = restAdapter.create(ApiService.class);
UserManager userManager = UserManager.getInstance(apiService);
UserDataStore userDataStore = UserDataStore.getInstance(
getSharedPreferences("prefs", MODE_PRIVATE)
);
//Presenter is initialized here
presenter = new LoginActivityPresenter(this, userManager, userDataStore);
}
}

他看起来不太友好不是吗?
这就是DI框架需要解决的问题,相同功能的代码看起来像这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class LoginActivity extends AppCompatActivity {
//通过@Inject标注来获取初始化这个类需要注入的依赖
@Inject
LoginActivityPresenter presenter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//Satisfy all dependencies requested by @Inject annotation
getDependenciesGraph().inject(this);
}
}

简单多了对吧!当然,DI框架也不是说不用去创建依赖类的对象实例,这些实例需要我们在一些恰当的地方去初始化和配置。但是这些对象的构建会从使用中分离出来(这个就是DI的准则)。DI框架关心的是怎么把他们联系起来(如何传递对象,并把他们放入到需要的位置)。