# Dagger2 + MVP + DI 模块化学习笔记

2018-03-21  本文已影响397人  xbigfat

[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 ---硬编码

硬编码的缺点有两个方面


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的几个注意:

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也添加进来了。


以上内容修改、摘抄于他人博客。原博客链接戳这里

上一篇下一篇

猜你喜欢

热点阅读