美文网首页
# Dagger2 + MVP + DI 模块化学习笔记

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

作者: xbigfat | 来源:发表于2018-03-21 21:14 被阅读397次

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


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

    相关文章

      网友评论

          本文标题:# Dagger2 + MVP + DI 模块化学习笔记

          本文链接:https://www.haomeiwen.com/subject/fcmnqftx.html