# Dagger2 + MVP + DI 模块化学习笔记
[TOC]
依赖注入
在软件工程领域,依赖注入(Dependency Injection)是用于实现控制反转(Inverse of Control)的常见方式之一。本文介绍依赖注入原理和常见的实现方式。
什么是依赖关系?
class A 中有一个类型为 class B 的成员变量,那么称 A 依赖 B
1.为什么需要依赖注入
控制反转用于解耦,解耦对象分别是谁?例子说明问题:
public class MovieLister{
private MovieFinder finder;
public MovieLister(){
finder = new MovieFinderImpl();
}
public Movie[] moviesDirectedBy(String arg){
List allMovies = finder.findAll();
for(Iterator it = allMovies.iterator();it.hasNext();{
Movie movie = (Movie)it.next();
if(!movie.getDirector().equals(arg))it.remove;
}
return (Movie[]) allMovies.toArray(new Movie[allMovies.size()]);
}
}
public interface MovieFinder{
List findAll();
}
创建了一个名为MovieLister的类来提供需要的电影列表,它 moviesDirectedBy() 方法根据导演名称来搜索电影。真正负责搜索电影的是实现了MovieFinder的接口的MovieFinderImpl 对象。我们的MovieLister的类在构造方法中创建了一个MovieFinderImpl的对象。
目前看来,一切都不错,但是当我们希望修改finder,将finder替换为新的实现时(eg:为MovieFinder增加一个参数表明Movie的数据来源),我们不仅需要修改MovieFinderImpl类,还需要修改MovieLister中的构造函数。
这就是依赖注入要处理的耦合。这种在MovieLister中创建MovieFinderImpl的方式,使得MovieLister不仅仅依赖于MovieFinder这个接口,它还依赖于MovieListImpl这个实现。这种在一个类中,直接创造另外一个类的对象的代码,是一种导致耦合的情况,可以称为硬初始化。
note: 在class A 中的成员变量为 interface B,使用时创建了 B 的实现类 C,当 B 改变时,需要同时改变 class A 和 class C ---硬编码
硬编码的缺点有两个方面
- 实现上述修改,需要修改创建 class C 处的代码
- 不便于测试。class A 无法单独被测试,其行为和 class C 耦合在一起,同时会降低代码的可读性。
2.依赖注入的实现方式
依赖注入并不神奇,日常的很多地方都用到了依赖注入。3种方式进行依赖注入。
2.1 构造函数注入
修改上面代码中MovieList的构造函数,使得MoviefinderImpl的实现在MovieLister类之外创建。这样,MovieLister只依赖于接口 MovieFinder,不依赖实现类。
public class MovieLister{
private MovieFinder finder;
public MovieLister(MovieFinder finder){
this.finder = finder;
}
...
}
2.2 setter注入
类似的,也可以通过增加一个 setter 方法传入创建好的MovieFinder对象,同样可以避免硬编码
public class MovieLister{
...
public void setFinder(MovieFinder finder){
this.finder = finder;
}
}
2.3接口注入
接口注入使用接口来提供setter方法,如下:
1.首先创建一个注入使用的接口。
public interface InjectFinder{
void injectFinder(MovieFinder finder);
}
2.MovieLister实现这个接口
class MovieLister implements InctFinder{
...
public void injectFinder(MovieFinder finder){
this.finder = finder;
}
...
}
3.通过调用 movieLister.injectFinder(finder)传递实现类。
3.最后
依赖注入降低了依赖和被依赖类型间都耦合。在修改被依赖都类型是,不需要修改依赖类的实现。同样,对于依赖类型的测试,可以通过 mocking object替代原有的被依赖类型,达到对依赖对象进行单元测试。
最后,依赖注入只是控制反转的一种实现方式,还有一种方式为依赖查找。
Dagger2
基本使用
一个例子:通过Dagger2来向 Activity 注入一些成员变量,使用MVP模式。内容是通过注入一个 Presrnter ,然后通过 Presenter 来设置 TextView 显示对内容为 user.name。
先来看 User 实体类。
public class User{
public String name;
public User(String name){
this.name = name;
}
}
再来看看 Presenter 类
public class Presenter{
DaggerActivity activity;
User user;
public Presrnter(DaggerActivity activiy,User user){
this.activity = activity;
this.user = user;
}
public void showUsername(){
activity.showUsername(user.name)
}
}
现在的场景是有一个DaggerActicity,持有一个Presenter成员,如何使用Dagger2来注入成员呢?
public class DaggerActivity{
//如何注入依赖?
private Presenter presenter;
...
}
1.第一步编写 Module
编写一个 ActivityModule 如下:
@Module
public class ActivityModule{
private DaggerActicity activity;
public ActivityModule(DaggerActivity activity){
this.activity = activity;
}
@Provides
public DaggerActivity provideActivity(){
return activity;
}
@Provides
public User provideUser(){
return new User("User from ActivityModule");
}
@Provides
public Presente providePresenter(){
return ne Presenter(activity,user);
}
}
ActivityModule 中的构造函数需要传入一个 DaggerActivity,
Module 的作用就是用来提供以来对象的。比如 现在需要注入 Presenter ,那么这个Module的作用就是生成一个 Presenter 对象,来让 Dagger2 注入到 DaggerActivity中。
所以我们写了一个方法 providePresenter() ,对这个方法使用 @Provides 注解,传入 Presrnter 所需要的参数。
那么这个 activity 和 user 从何而来呢?
其实就是从上面另外两个方法中获取到的。
编写Module的几个注意:
- 类需要用 @Module 来标记
- @Provides 注解标记提供对象的方法。方法名需要用provide来开头。
2.编写AvtivityComponent
@Component(modules = ActivityModule.class)
public interface ActivityComponent{
void inject(DaggerActivity activity)
}
用 @Component 来表示一个接口,声明 module 为上面编写到 ActivityComponent ,提供一个 inject方法,提供注入参数的类型。
3.Build - Make Project
make 之后,apt会自动生成一个以Dagger开头到Component,可以直接使用这个类。
4.注入到Activity 中
在Activity中的 onCreate 方法中,写如下代码:
DaggerActivityComponent.builder()
.activityModule(new ActivityModule(this))
.build()
.inject(this);
首先调用了这个类的 builder(),然后调用
activityModule 指定了 module 是 ActivityModule.class ,传入一个新对象进去。
到此为止,已经使用Dagger2形成了关联,我们还需要注入Presenter,在Activity中:
@Inject
Presenter presenter;
即可。
下面是 Activity 类的完整代码
public class DaggerActivity extends AppCompatActivity{
TextView textview;
@Inject
Presenter presenter;
@Override
protected void onCreated(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
text = (TextView) findViewById(R.id.text);
inject();
presenter.showUsername();
}
private void inject(){
DaggerActivityComponent.builder().activityModule(new ActivityModule.build()
.inject(this);
}
private vois showUsername(String name){
text.setText(name);
}
}
上面的代码运行起来的结果就是在 DaggerActivity 的 TextView 中显示了一串子夫,实现了依赖注入。
新需求
现在我们希望提供一个全局的 OkHttpClient 和 Retrofit 对象来进行网络请求,他们的生命长度应该是贯穿整个 App 的,这个时候就要定制 Appcomponent了。
1.编写 Module
@Module
public class ApiModule{
public static final String End_point = "https://www.baidu.com";
@Provides
@Singleton
OkHttpClient provideOkHttpClient(){
OkHttpClient client = new OkHttpClient.Builder()
.connectTimeout(60 * 1000, TimeUnit.MILLISECONDS)
.readTimeout(60 * 1000, TimeUnit.MILLISECONDS)
.build();
return client;
}
@Provides
@Singleton
Retrofit provideRetrofit(OkHttpClient client) {
Retrofit retrofit = new Retrofit.Builder()
.client(client)
.baseUrl(END_POINT)
.build();
return retrofit;
}
@Provides
@Singleton
User provideUser(){
return new User("name form ApiProvide");
}
}
请注意,我这里的 provide 方法额外添加了一个 @SingleTon 注解,这里说明是全局单例的对象,而且我这里改动了一小部分代码,把 ActivityModule 的 provideUser 移动到这里来了, 我这里是为了演示依赖过程。
2.编写 AppComponent
@Singleton
@Component(modules = {ApiModule.class})
public interface AppComponent {
OkHttpClient getClient();
Retrofit getRetrofit();
User getUser();
}
这里的 AppComponent 提供了 3 个方法,分别用来暴露 OkHttpClient 、Retrofit 和 User 对象。
3.Make 实例化
在 Application 中实例化这个 component。
public class MyApplication extends Application {
AppComponent appComponent;
@Override
public void onCreate() {
super.onCreate();
appComponent = DaggerAppComponent.builder()
.apiModule(new ApiModule())
.build();
}
public AppComponent getAppComponent() {
return appComponent;
}
}
4.编写 activityComponent
@ActivityScope
@Component(modules = ActivityModule.class,dependencies = AppComponent.class)
public interface ActivityComponent {
void inject(DaggerActivity daggerActivity);
}
改动的地方呢是添加了一个 @ActivityScope 然后,添加了一个dependencies = AppComponent.class。没错,Component之间也可以依赖的。
5.编写 DaggerActicity
public class DaggerActivity extends AppCompatActivity {
private static final String TAG = "DaggerActivity";
TextView text;
@Inject
DaggerPresenter presenter;
@Inject
OkHttpClient client;
@Inject
Retrofit retrofit;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_dagger);
text = (TextView) findViewById(R.id.text);
inject();
presenter.showUserName();
Log.i(TAG, "client = " + (client == null ? "null" : client));
Log.i(TAG, "retrofit = " + (retrofit == null ? "null" : retrofit));
}
private void inject() {
AppComponent appComponent = ((MyApplication) getApplication()).getAppComponent();
DaggerActivityComponent.builder()
.appComponent(appComponent)
.activityModule(new ActivityModule(this))
.build().inject(this);
}
public void showUserName(String name) {
text.setText(name);
}
}
这里添加了两个注入,分别注入了一个OkHttpClient和一个Retrofit对象,然后在注入的时候也把AppComponent也添加进来了。
以上内容修改、摘抄于他人博客。原博客链接戳这里