介绍依赖注入的相关知识
依赖注入 Dependency injection
依赖注入,就是构建对象并在我们需要时把他们传入。
想象有一个简单的类,UserManager
, 他依赖UserStore
与ApiService
。如果没有依赖注入,这个类看起来会是这个样子:
UserStore
和ApiServie
两者都是在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; 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; 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
的代码。两个依赖的对象都是通过外面提供的。
所以使用依赖注入的优势是什么???
优势
构造/使用 的分离
当我们构造类实例,通常这些对象会在其他地方使用到,多亏这个方法让我们的代码更加模块化,所有依赖都可以很简单的被替换掉,并且不会与我们应用的逻辑产生冲突,想要改变DataBaseUserStore
为SharedPrefesUserStore
?好的,我们只需要关心公开的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() { } }
|
它只能使用DI,多亏UserManger
是完全独立于UserStore
与ApiService
实现的。我们可以提供这些类的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 = 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 LoginActivityPresenter presenter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); getDependenciesGraph().inject(this); } }
|
简单多了对吧!当然,DI框架也不是说不用去创建依赖类的对象实例,这些实例需要我们在一些恰当的地方去初始化和配置。但是这些对象的构建会从使用中分离出来(这个就是DI的准则)。DI框架关心的是怎么把他们联系起来(如何传递对象,并把他们放入到需要的位置)。