17:最好之起草

  • 查看
  • 提交
  • 统计
  • 提问

归根结底时间限定: 
10000ms

单个测试点时间范围: 
1000ms

内存限制: 
65536kB

描述
奶牛Bessie计划好好享用柔软的春天新草。新草分布在R行C列的牧场里。它想计算一下牧场被的草丛数量。

当牧场地图中,每个草丛要么是单科“#”,要么是发国有边的隔壁两单“#”。给定牧场地图,计算起多少只草丛。

譬如说,考虑如下5实施6排列的牧场地图

.#....
..#...
..#..#
...##.
.#....

夫牧场有5个草丛:一个当首先执,一个以第二排横跨了亚、三行,一个每当第三实行,一个在第四实行横跨了季、五列,最后一个于第五推行。

 

输入
首先履行包含两只整数R和C,中间用么空格隔开。
接通下R行,每行C个字符,描述牧场地图。字符只有“#”或“.”两种。(1 <= R,
C <= 100 )

输出
出口一个平头,表示草丛数。

样例输入
5 6
.#….
..#…
..#..#
…##.
.#….

样例输出
5

来源
USACO Open 2008 Bronze

 

 

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cstdlib>
 4 using namespace std;
 5 char a[1001][1001];
 6 int now=1;
 7 int m_tot=0;
 8 int z_tot=0;
 9 int ans=0;
10 int main() 
11 {
12     int n,m;
13     cin>>n>>m;
14     for(int i=0;i<n;i++)
15     {
16         for(int j=0;j<m;j++)
17         {
18             cin>>a[i][j];
19         }
20     }
21     for(int i=0;i<n;i++)
22     {
23         for(int j=0;j<m;j++)
24         {
25             if(a[i][j]=='#')
26             {
27                 if(a[i+1][j]=='#')
28                 {
29                     a[i][j]='.';
30                     a[i+1][j]='.';
31                     ans++;
32                 }
33                 else if(a[i-1][j]=='#')
34                 {
35                     a[i][j]='.';
36                     a[i+1][j]='.';
37                     ans++;
38                 }
39                 else if(a[i][j+1]=='#')
40                 {
41                     a[i][j]='.';
42                     a[i][j+1]='.';
43                     ans++;
44                 }
45                 else if(a[i][j-1]=='#')
46                 {
47                     a[i][j]='.';
48                     a[i+1][j]='.';
49                     ans++;
50                 }
51                 else
52                 {
53                     a[i][j]='.';
54                     ans++;
55                 }
56             }
57         }
58     }
59     cout<<ans;
60     return 0;
61 }

 

注:

  1. 代码中之 //<= 表示新加之、修改的相当得着重关注之代码
  2. Class#method表示一个接近的instance method,比如
    LoginPresenter#login 表示 LoginPresenter的login(非静态)方法。

问题

在眼前一模一样篇稿子倍受,我们讲述了依靠注入的定义,以及借助注入对单元测试极其重大的关键及必要性。在那么篇稿子的尾声,我们相遇了一个题目,那就是是要非采用DI框架,而全用手工来举行DI的语句,那么富有的Dependency都急需以太上层的client来转,这只是免是起好工作。继续为此我们眼前的例子来具体说明一下。
假如来一个签到界面,LoginActivity,他生一个LoginPresenterLoginPresenter用到了UserManagerPasswordValidator,为了吃问题易得又显一点,我们设UserManager用到SharedPreference(用来储存一些用户的着力设置等)和UserApiService,而UserApiService同时待由Retrofit创建,而Retrofit又用到OkHttpClient(比如说你如自己主宰timeout、cache等物)。
使用DI模式,UserManager的筹划如下:

public class UserManager {
    private final SharedPreferences mPref;
    private final UserApiService mRestAdapter;

    public UserManager(SharedPreferences preferences, UserApiService userApiService) {
        this.mPref = preferences;
        this.mRestAdapter = userApiService;
    }

    /**Other code*/
}

LoginPresenter的规划如下:

public class LoginPresenter {
    private final UserManager mUserManager;
    private final PasswordValidator mPasswordValidator;

    public LoginPresenter(UserManager userManager, PasswordValidator passwordValidator) {
        this.mUserManager = userManager;
        this.mPasswordValidator = passwordValidator;
    }

    /**Other code*/
}

于这种景象下,最终之client
LoginActivity里面如new一个presenter,需要举行的事体如下:

public class LoginActivity extends AppCompatActivity {
    private LoginPresenter mLoginPresenter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        OkHttpClient okhttpClient = new OkHttpClient.Builder()
                .connectTimeout(30, TimeUnit.SECONDS)
                .build();
        Retrofit retrofit = new Retrofit.Builder()
                .client(okhttpClient)
                .baseUrl("https://api.github.com")
                .build();
        UserApiService userApiService = retrofit.create(UserApiService.class);
        SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
        UserManager userManager = new UserManager(preferences, userApiService);

        PasswordValidator passwordValidator = new PasswordValidator();
        mLoginPresenter = new LoginPresenter(userManager, passwordValidator);
    }
}

以此啊最为夸张了,LoginActivity所用之,不过大凡一个LoginPresenter而已,然而其可要掌握LoginPresenter的Dependency是什么,LoginPresenter的Dependency的Dependency又是啊,然后new一堆放物出来。而且可以预见的是,这个app的其它地方啊亟需这里的OkHttpClientRetrofitSharedPreferenceUserManager等等dependency,因此呢需要new这些东西下,造成大气之代码重复,和无必要的object
instance生成。然而如果前所述,我们又不能不用到DI模式,这个怎么处置呢?

思,如果会上这样的作用,那该来差不多好:我们无非需要在一个看似于dependency工厂的地方联合生产这些dependency,以及这些dependency的dependency。所有需要因此到这些Dependency的client都自之厂内去赢得。而且还美好的凡,一个client(比如说LoginActivity)只需要明白它直接用到之Dependency(LoginPresenter),而无欲了解其的Dependency(LoginPresenter)又就此到什么Dependency(UserManagerPasswordValidator)。系统自动识别出这依靠关系,从工厂内将用之Dependency找到,然后拿此client所需要的Dependency创建出来。

发这样一个事物,帮咱落实这意义呢?相信聪明的您曾经蒙到了,回答是得之,它就是是我们今天设介绍的dagger2。

解药:Dagger2

于dagger2里面,负责生产这些Dependency的联结工厂叫做 Module
,所有的client最终是如果自module里面获取Dependency的,然而他们无是直通往module要的,而是发一个专门的“工厂管理员”,负责接收client的渴求,然后到Module里面去找到相应的Dependency,提供被client们。这个“工厂管理员”叫做
Component。基本上,这是dagger2里面最好着重之简单只概念。

下面,我们来看看这片个概念,对许到代码里面,是怎么的。

生产Dependency的工厂:Module

第一是Module,一个Module对许交代码里面就是是一个近似,只不过这个近乎需要为此dagger2里面的一个annotation
@Module来标注一下,来代表马上是一个Module,而无是一个寻常的类。我们说Module是产Dependency的地方,对许到代码里面就是Module里面来成千上万办法,这些方法做的作业虽是开创Dependency。用者的例证中之Dependency来说明:

@Module
public class AppModule {

    public OkHttpClient provideOkHttpClient() {
        OkHttpClient okhttpClient = new OkHttpClient.Builder()
                .connectTimeout(30, TimeUnit.SECONDS)
                .build();
        return okhttpClient;
    }

    public Retrofit provideRetrofit(OkHttpClient okhttpClient) {
        Retrofit retrofit = new Retrofit.Builder()
                .client(okhttpClient)
                .baseUrl("https://api.github.com")
                .build();
        return retrofit;
    }
}

在上面的Module(AppModule)中,有少数单办法provideOkHttpClient()provideRetrofit(OkHttpClient okhttpClient),分别创建了点儿独Dependency,OkHttpClientRetrofit。但是也,我们为说了,一个Module就是一个像样,这个看似产生一对生育Dependency的计,但它吗可生出一些正规的,不是用来生产Dependency的艺术。那哪让管理员知道,一个Module里面哪些方法是因此来养Dependency的,哪些不是吗?为了方便做此分,dagger2规定,所有生产Dependency的办法要用
@Provides斯annotation标注一下。所以,上面的
AppModule科学的写法应该是:

@Module
public class AppModule {
    @Provides
    public OkHttpClient provideOkHttpClient() {
        OkHttpClient okhttpClient = new OkHttpClient.Builder()
                .connectTimeout(30, TimeUnit.SECONDS)
                .build();
        return okhttpClient;
    }

    @Provides
    public Retrofit provideRetrofit(OkHttpClient okhttpClient) {
        Retrofit retrofit = new Retrofit.Builder()
                .client(okhttpClient)
                .baseUrl("https://api.github.com")
                .build();
        return retrofit;
    }
}

这种用来养Dependency的、用
@Provides修饰了的法吃Provider方法。这里而注意第二独Provider方法
provideRetrofit(OkHttpClient okhttpClient),这个法子有一个参数,是OkHttpClient。这是为缔造一个Retrofit对象需要一个OkHttpClient的靶子,这里透过参数传递进来。这样做的补是,当Client向管理员(Component)索要一个Retrofit的时刻,Component会自动找到Module里面找到生产Retrofit的斯
provideRetrofit(OkHttpClient okhttpClient)方法,找到后试图调用这个办法创建一个Retrofit对象,返回给Client。但是调用这个主意需要一个OkHttpClient,于是Component又会失掉摸其它的provider方法,看看发生没有来哪个会养OkHttpClient。于是就找到了上面的率先只provider方法:
provideOkHttpClient()。找到后,调用这个办法,创建一个OkHttpClient对象,再调用
provideRetrofit(OkHttpClient okhttpClient)方法,把刚创建的OkHttpClient目标传上,创建有一个Retrofit对象,返回给Client。当然,如果最终找到的
provideOkHttpClient()道呢需外参数,那么管理员还会见持续递归的觅下去,直到所有的Dependency都为满足了,再一个一个创建Dependency,然后将最后Client需要的Dependency呈递给Client。
杀好,现在我们把文章开始的例证中之拥有Dependency都因此这种方法,在
AppModule其中声明一个provider方法:

@Module
public class AppModule {
    @Provides
    public OkHttpClient provideOkHttpClient() {
        OkHttpClient okhttpClient = new OkHttpClient.Builder()
                .connectTimeout(30, TimeUnit.SECONDS)
                .build();
        return okhttpClient;
    }

    @Provides
    public Retrofit provideRetrofit(OkHttpClient okhttpClient) {
        Retrofit retrofit = new Retrofit.Builder()
                .client(okhttpClient)
                .baseUrl("https://api.github.com")
                .build();
        return retrofit;
    }

    @Provides
    public UserApiService provideUserApiService(Retrofit retrofit) {
        return retrofit.create(UserApiService.class);
    }

    @Provides
    public SharedPreferences provideSharedPreferences(Context context) {
        return PreferenceManager.getDefaultSharedPreferences(context);
    }

    @Provides
    public UserManager provideUserManager(SharedPreferences preferences, UserApiService service) {
        return new UserManager(preferences, service);
    }

    @Provides
    public PasswordValidator providePasswordValidator() {
        return new PasswordValidator();
    }

    @Provides
    public LoginPresenter provideLoginPresenter(UserManager userManager, PasswordValidator validator) {
        return new LoginPresenter(userManager, validator);
    }
}

方的代码如果您仔细看之话语,会发现一个题目,那便是中的SharedPreference
provider方法
provideSharedPreferences(Context context)内需一个context对象,但是
AppModule中连从未context
的Provider方法,这个怎么处置呢?对于这题目,你可重创一个context
provider方法,但是context对象从哪来吗?我们得自定义一个Application,里面提供一个静态方法返回一个context,这种做法相信大家还关涉了。Application类如下:

public class MyApplication extends Application {
    private static Context sContext;

    @Override
    public void onCreate() {
        super.onCreate();
        sContext = this;
    }

    public static Context getContext() {
        return sContext;
    }
}

provider方法如下:

    @Provides
    public Context provideContext() {
        return MyApplication.getContext();
    }

只是这种方式无是大好,为什么吧,因为context的获得一定于是写死了,只能从MyApplication.getContext(),如果测试环境下想把Context换成别的,还要吃MyApplication定义一个setter,然后调用MyApplication.setContext(…),这个就绕的出接触多。更好之做法是,把Context作为
AppModule的一个组织参数,从外围传进(应用DI模式,还记也?):

@Module
public class AppModule {
    private final Context mContext;

    public AppModule(Context context) {
        this.mContext = context;
    }

    @Provides
    public Context provideContext() {
        return mContext;
    }

    //其他的provider方法

}

不错,一个Module就是一个例行的接近,它为得起构造方法,以及另正常类的特点。你恐怕会见怀念那么被构造函数的context对象由哪来吗?别急,这个题材马上解答。

Dependency工厂管理员:Component

前我们说话了dagger2的一半,就是生产Dependency的厂:Module。接下来我们说另一半,工厂管理员:Component。跟Module不同的是,我们当落实Component时,不是概念一个像样,而是定义一个接口(interface):

public interface AppComponent {
}

名字可以随便取,跟Module需要为此
@Module修饰一下接近的,一个dagger2的Component需要用
@Component修饰一下,来号这是一个dagger2的Component,而无是一个屡见不鲜的interface,所以是的概念方式是:

@Component
public interface AppComponent {
}

每当事实上情况遇,可能发生多独Module,也说不定产生差不多个Component,那么当Component接收及一个Client的Dependency请求时,它怎么亮要起哪个Module里面去寻觅这些Dependency呢?它不可能遍历我们的各个一个类,然后搜索有具有的Module,再遍历所有Module的Provider方法,去摸Dependency,这样先不说能免能够就,就终于做得到,效率为最好没有了。因此dagger2规定,我们于定义Component的时节,必须指定这个管理员“管理”哪些工厂(Module)。指定的不二法门是,把用是Component管理的Module传给
@Component斯注解的modules属性(或者叫道?),如下:

@Component(modules = {AppModule.class})  //<=
public interface AppComponent {
}

modules属性接收一个屡组,里面是者Component管理之有着Module。在地方的例证中,AppComponent只管理AppModule一个。

Component给Client提供Dependency的方法

眼前我们说了Module和Component的贯彻,接下就是Component怎么为Client提供Dependency的问题了。一般的话,有零星种植,当然总共不止这简单种,只不过这简单栽最常用,也最为好理解,一般的话用就片种就够用了,因此这里不赘述其他的点子。

方式同样:在Component里面定义一个回来Dependency的法门

第一种植是以Component里面定义一个返Dependency的法,比如LoginActivity需要LoginPresenter,那么我们可于AppComponent个中定义一个赶回LoginPresenter的方法:

@Component(modules = {AppModule.class})
public interface AppComponent {
    LoginPresenter loginPresenter();
}

若可能会见奇怪,为什么Component只需要定义成接口就执行了,不是应定义一个类似,然后自己以Module去开就宗事为?如果是这样的话,那就最low了。dagger2的工作原理是,在您的java代码编译成字节码的过程遭到,dagger2会对拥有的Component(就是用
@Component修饰了的interface)进行拍卖,自动生成一个兑现了这个interface的近乎,生成的类名是Component的讳前加上“Dagger”。比如我们定义之
AppComponent,对应的自动生成的好像叫做DaggerAppComponent。我们了解,实现一个interface需要贯彻中的装有术,因此,DaggerAppComponent是贯彻了
loginPresenter();其一措施的。实现的主意大概就是从 AppComponent管理的
AppModule内部去探寻LoginPresenter的Provider方法,然后调用这个法子,返回一个LoginPresenter

为此,使用这种艺术,当Client需要Dependency的时段,首先得因此DaggerAppComponent是近乎创建一个靶,然后调用这个目标的
loginPresenter()法,这样Client就可知获取一个LoginPresenter了,这个DaggerAppComponent目标的创办和采取方式如下:

public class LoginActivity extends AppCompatActivity {
    private LoginPresenter mLoginPresenter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        AppComponent appComponent = DaggerAppComponent.builder().appModule(new AppModule(this)).build();  //<=
        mLoginPresenter = appComponent.loginPresenter();   //<=
    }
}

总结一下,我们到今寿终正寝,做了呀:

  1. 咱们定义了一个 AppModule接近,里面定义了有些Provider方法
  2. 概念了一个
    AppComponent,里面定义了一个赶回LoginPresenter的方法loginPresenter()

就是这样,我们即便足以运用
DaggerAppComponent.builder().appModule(new AppModule(this)).build().loginPresenter();
来获取一个LoginPresenter对象了。
及时简直就是是magic,不是也?
如未是dagger2,而是我们团结来兑现这AppComponent
interface,想想咱们要举行哪些工作:

  1. 概念一个Constructor,接受一个AppModule对象,保存在field中(mAppModule)
  2. 实现loginPresenter()方法,调用mAppModule的provideLoginPresenter(UserManager userManager, PasswordValidator validator)方式,这时候发现这个措施需要少只参数
    UserManagerPasswordValidator
  3. 调用provideUserManager(SharedPreferences preferences, UserApiService service)来得到一个UserManager,这时候发现此方式以用简单个参数
    SharedPreferencesUserApiService
  4. 调用provideSharedPreferences(Context context)来获取一个SharedPreference,这时候发现先使发出一个context
  5. 。。。
  6. 。。。
  7. 。。。

说白了,就是将稿子开始我们刻画的那段代码又实现了扳平普,而采取dagger2,我们尽管举行了前描述的有数码事要是都,这间错综复杂的Dependency关系dagger2帮我们机关理清矣,生成对应的代码,去调用相应的Provider方法,满足这些靠关系。
兴许这里举得这个事例不足以让您道出啊特别不了之,但是你一旦明,一个正常的App,可不只发生一个Login
page而已,稍微大点的App,Dependency都起几百甚至上千只,对于服务器程序来说,Dependency则更多。对于当下点,大家可以错过押Dagger2主要作者的是视频,他里头涉及了Google一个android
app有3000行代码专门来保管Dependency,而一个Server
app甚至闹10万实施如此的代码。这个时段要错过手动new这些dependency、并且要因为正确的一一new出来,简直会使活命。而且让问题更费时的是,随着app的演进需求的转,Dependency之间的干也于动态的变。比如说UserManager不再采用SharedPreference,而是以database,这个时刻UserManager的构造函数里面少了一个SharedPreferences,多了一个DatabaseHelper然的东西,那么要使用正规的点子管理Dependency,所有new UserManager的地方还设改,而是用dagger2,你唯有待以
AppModule其间上加一个DatabaseHelper
Provider方法,同时将UserManager的provider方法第一参数从SharedPreferences改成DatabaseHelper就哼了,所有以UserManager的地方不需要做任何变更,LoginPresenter不需举行其他变动,LoginActivity非欲另移,这难道说不是magic吗?

说点题外话,这种将题目(我们这里是乘关系)描述下,而未是拿贯彻过程写出来的编程风格让Declarative
programming,跟它对应的让Imperative
Programming,相对于后者,前者的优势是:可读性更胜,side
effect更少,可扩展性更强之类。这是相同种植编程风格,跟语言、框架无关。当然,有的言语或框架天生就是会为程序员更便于之采用这种style来编程。这面极端显眼的当属Prolog,有趣味的可去询问下,绝对mind-blowing!
对于Java或Android开发者来说,想为我们的代码更加declarative,最好的方是采取dagger2和RxJava。

方法二:Field Injection

话说回来,我们继续介绍dagger2,前面我们介绍了Component给Client提供Dependency的第一栽方式,接下继续介绍第二种方法,这种艺术叫
Field injection
。这里我们后续为此LoginActivity的事例来验证,LoginActivity待一个LoginPresenter。那么下这种措施的做法是,我们就于LoginActivity里头定义一个LoginPresenter的field,这个field需要采取
@Inject修饰一下:

public class LoginActivity extends AppCompatActivity {
    @Inject                             //<=
    LoginPresenter mLoginPresenter;     //<=

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}

然后在onCreate()里面,我们把DaggerAppComponent对象创建出来,调用这个目标的inject方法,把LoginActivity传进去:

public class LoginActivity extends AppCompatActivity {
    @Inject                             
    LoginPresenter mLoginPresenter;     

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        AppComponent appComponent = DaggerAppComponent.builder().appModule(new AppModule(this)).build(); //<=
        appComponent.inject(this); //<=

        //从此之后,mLoginPresenter就被实例化了
        //mLoginPresenter.isLogin()
    }
}

本,我们用先以AppComponent个中定义一个inject(LoginActivity loginActivity)方法:

@Component(modules = {AppModule.class})
public interface AppComponent {
    void inject(LoginActivity loginActivity);  //<=
}

DaggerAppComponent实现这个艺术的计是,去LoginActivity中间所有给
@Inject修饰的field,然后调用
AppModule相应的Provider方法,赋值给此field。这里需要留意的是,@Inject
field不克而private,不然dagger2找不交者field。
寻常来说,这种方式比较第一种艺术又简短,代码也再也简洁。假设LoginActivity还需要外的Dependency,比如要一个统计打点的Dependency(StatManager),那么你不过需要以AppModule内部定义一个Provider方法,然后以LoginActivity里声明另外一个field就哼了:

public class LoginActivity extends AppCompatActivity {
    @Inject                             
    LoginPresenter mLoginPresenter;  

    @Inject
    StatManager mStatManager;   //<=

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        AppComponent appComponent = DaggerAppComponent.builder().appModule(new AppModule(this)).build();
        appComponent.inject(this);
    }
}

无论有多少只@Inject
field,都不过待调用一破appComponent.inject(this);。用过了您就算会见以为,恩,好爽!
只是,需要专注的某些凡是,这种方式不支持继承,比如说LoginActivity累自一个
BaseActivity,而@Inject StatManager mStatManager;是放在BaseActivity里面的,那么在LoginActivity内部调用
appComponent.inject(this);连无见面给BaseActivity里面的
mStatManager取实例化,你得于
BaseActivity中为调整用同一涂鸦appComponent.inject(this);

@Singleton和Constructor Injection

到此地,Client从Component获取Dependency的简单种办法尽管介绍完。但是此间发出只问题,那即便是历次Client向Component索要一个Dependency,Component都见面创造一个初的下,这或者会见促成资源的浪费,或者说很多时不是咱怀念如果的,比如说,SharedPreferencesUserManagerOkHttpClient,
Retrofit这些还只需要一致卖便好了,不待每次都创造一个instance,这个时节咱们可以为这些Dependency的Provider方法加上@Singleton就好了。如:

@Module
public class AppModule {

    @Provides
    @Singleton          //<=
    public OkHttpClient provideOkHttpClient() {
        OkHttpClient okhttpClient = new OkHttpClient.Builder()
                .connectTimeout(30, TimeUnit.SECONDS)
                .build();
        return okhttpClient;
    }

    //other method
}

诸如此类,当Client第一蹩脚呼吁一个OkHttpClient,dagger2会创建一个instance,然后保留下来,下同样不成Client再次请求一个OkHttpClient是,dagger2会直接返回上次创设好之,而毫无还创造instance。这便相当给用相同栽更省心、而且DI-able的章程实现了singleton模式。

此地还给大家一个bonus,如果您切莫待举行单元测试,而只是以dagger2来开DI,组织app的构造来说,其实AppModule其间的重重Provider方法是不待定义之。比如说在这种情景下,LoginPresenter的Provider方法
provideLoginPresenter(UserManager userManager, PasswordValidator validator)
就非需定义,你只待在概念LoginPresenter的时候,给它的Constructor加上
@Inject修饰一下:

public class LoginPresenter {
    private final UserManager mUserManager;
    private final PasswordValidator mPasswordValidator;

    @Inject
    public LoginPresenter(UserManager userManager, PasswordValidator passwordValidator) {
        this.mUserManager = userManager;
        this.mPasswordValidator = passwordValidator;
    }

    //other methods
}

dagger2会自动创建是LoginPresenter所需要的Dependency是她能提供的,所以会见去Module里面找到这LoginPresenter所需的Dependency,交给LoginPresenter的Constructor,创建好立Dependency,交给Client。这实际上呢是Client通过Component使用Dependency的一律种方法,叫
Constructor injection (上同样篇稿子为事关Constructor
injection
,不过小有接触不同,注意区分一下)同样的,在那种状态下,UserManager的Provider方法也无待定义,而单独待吃UserManager的Constructor加上一个@Inject就算哼了。说白了,你只待为那些休是透过Constructor来创造的Dependency(比如说SharedPreferences、UserApiService等)定义Provider方法。
有了 Constructor injection
,我们的代码又能够获取越来越的简化,然而遗憾之凡,这种方式将造成我们做单元测试的时光无法mock这当中的Dependency。说及单元测试,我们转移忘了此系列之主题T_T。。。那么接下就介绍dagger2在单元测试里面的利用,以及为什么
Constructor injection 将招致单元测试里面无法mock这个Dependency。

dagger2以单元测试里面的运用

每当介绍dagger2在单元测试里面的运前,我们先行改进一下面前的代码,我们创建DaggerAppComponent的地方是以LoginActivity,其实这么非是怪好,为什么吧?想想如果login以后别地方吗需UserManager,那么我们而如开创一个DaggerAppComponent,这种地方是不少的,毕竟
AppModule内部定义了有遍app都如因此到的Dependency,比如说Retrofit、SharedPreferences等等。如果每个需要利用的地方还创造同布满DaggerAppComponent,就造成了代码的再和内存性能的荒废,理论及来说,DaggerAppComponent靶全app只需要平等卖便哼了。所以我们于为此dagger2的时,一般的做法是,我们会以app启动的下创建好,放在某个一个地方。比如,我们于起定义的Application#onCreate()里面创建好,然后放在有地方,我个人习惯定义一个类似为ComponentHolder,然后推广里:

public class MyApplication extends Application {

    @Override
    public void onCreate() {
        super.onCreate();

        AppComponent appComponent = DaggerAppComponent.builder()
                                    .appModule(new AppModule(this))
                                    .build();
        ComponentHolder.setAppComponent(appComponent);
    }
}

public class ComponentHolder {
    private static AppComponent sAppComponent;

    public static void setAppComponent(AppComponent appComponent) {
        sAppComponent = appComponent;
    }

    public static AppComponent getAppComponent() {
        return sAppComponent;
    }
}

下一场在得 AppComponent的地方,使用
ComponentHolder.getAppComponent()来博一个DaggerAppComponent对象:

public class LoginActivity extends AppCompatActivity {
    @Inject
    LoginPresenter mLoginPresenter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        ComponentHolder.getAppComponent().inject(this);  //<=
    }
}

这样在用的地方,看起代码也根本了很多。
顶此,我们就可以介绍以单元测试里面怎么来mock
Dependency了。假设LoginActivity有有限个EditText和一个login
button,点击是button,将由个别单EditText里面得到用户称与密码,然后调用LoginPresenter的login方法:

public class LoginActivity extends AppCompatActivity {
    @Inject
    LoginPresenter mLoginPresenter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ComponentHolder.getAppComponent().inject(this);

        findViewById(R.id.login).setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                String username = ((EditText) findViewById(R.id.username)).getText().toString();
                String password = ((EditText) findViewById(R.id.password)).getText().toString();

                mLoginPresenter.login(username, password);
            }
        });
    }
}

我们现要测的,就是当用户点击是login
button的时刻,mLoginPresenter的login方法得到了调用,如果你看了这个系列之前面几篇文章,你尽管知这里的mLoginPresenter需要mock掉。但是,这里的mLoginPresenter是打dagger2的component里面得到的,这里怎么将mLoginPresenter换成mock呢?
我们在回首一下,其实LoginActivity只是向DaggerAppComponent索取了一个LoginPresenter,而DaggerAppComponent骨子里是调用了AppModule
provideLoginPresenter()方式来赢得了一个LoginPresenter,返回给LoginActivity,也就是说,真正生产LoginPresenter的地方是于
AppModule。还记得也,我们创建DaggerAppComponent的上,给它们的builder传递了一个AppModule对象:

public class MyApplication extends Application {

    @Override
    public void onCreate() {
        super.onCreate();

        AppComponent appComponent = DaggerAppComponent.builder()
                                    .appModule(new AppModule(this)) //<= 这里这里
                                    .build();
        ComponentHolder.setAppComponent(appComponent);
    }
}

其实DaggerAppComponent调用的AppModule靶,就是我们当创立它的时候传给大builder的。那么,如果我们传给DaggerAppComponentAppModule大凡一个mock对象,在是mock对象的provideLoginPresenter()被调用的时光,返回一个mock的LoginPresenter,那么LoginActivity取得的,不纵是一个mock的LoginPresenter了吗?
俺们用代码来促成转瞧是啊样子,这里因为LoginActivity是android相关的近乎,因此待用到robolectric这个framework,虽然这个我们还不曾介绍到,但是代码应该看得亮,如下:

@RunWith(RobolectricGradleTestRunner.class) //Robolectric相关,看不懂的话忽略
@Config(constants = BuildConfig.class, sdk = 21) //同上
public class LoginActivityTest {

    @Test
    public void testActivityStart() {
        AppModule mockAppModule = spy(new AppModule(RuntimeEnvironment.application)); //创建一个mockAppModule,这里不能spy(AppModule.class),因为`AppModule`没有默认无参数的Constructor,也不能mock(AppModule.class),原因是dagger2的约束,Provider方法不能返回null,除非用@Nullable修饰
        LoginPresenter mockLoginPresenter = mock(LoginPresenter.class);  //创建一个mockLoginPresenter
        Mockito.when(mockAppModule.provideLoginPresenter(any(UserManager.class), any(PasswordValidator.class))).thenReturn(mockLoginPresenter);  //当mockAppModule的provideLoginPresenter()方法被调用时,让它返回mockLoginPresenter
        AppComponent appComponent = DaggerAppComponent.builder().appModule(mockAppModule).build();  //用mockAppModule来创建DaggerAppComponent
        ComponentHolder.setAppComponent(appComponent);  //记得放到ComponentHolder里面,这样LoginActivity#onCreate()里面通过ComponentHolder.getAppComponent()获得的就是这里创建的appComponent

        LoginActivity loginActivity = Robolectric.setupActivity(LoginActivity.class); //启动LoginActivity,onCreate方法会得到调用,里面的mLoginPresenter通过dagger2获得的,将是mockLoginPresenter
        ((EditText) loginActivity.findViewById(R.id.username)).setText("xiaochuang");
        ((EditText) loginActivity.findViewById(R.id.password)).setText("xiaochuang is handsome");
        loginActivity.findViewById(R.id.login).performClick();    

        verify(mockLoginPresenter).login("xiaochuang", "xiaochuang is handsome");  //pass!
    }
}

即就算是dagger2在单元测试里面的运。基本上就是mock
Module的Provider方法,让其回到您想只要之mock对象。这吗说了为何说但所以
Constructor injection
的口舌,会招致Dependency无法mock,因为没有对号入座的Provider方法来叫我们mock啊。上面的代码看起也许你晤面当小多,然而事实上开支被,上面测试方法里的第1、4、5行都是通用的,我们得以管他们抽到一个辅助类里面:

public class TestUtils {
   public static final AppModule appModule = spy(new AppModule(RuntimeEnvironment.application));

   public static void setupDagger() {
       AppComponent appComponent = DaggerAppComponent.builder().appModule(appModule).build();
       ComponentHolder.setAppComponent(appComponent);
   }
}

这样咱们前的测试方法就可简化了:

public class LoginActivityTest {

    @Test
    public void testActivityStart() {
        TestUtils.setupDagger();
        LoginPresenter mockLoginPresenter = mock(LoginPresenter.class);
        Mockito.when(TestUtils.appModule.provideLoginPresenter(any(UserManager.class), any(PasswordValidator.class))).thenReturn(mockLoginPresenter);

        LoginActivity loginActivity = Robolectric.setupActivity(LoginActivity.class);
        ((EditText) loginActivity.findViewById(R.id.username)).setText("xiaochuang");
        ((EditText) loginActivity.findViewById(R.id.password)).setText("xiaochuang is handsome");
        loginActivity.findViewById(R.id.login).performClick();

        verify(mockLoginPresenter).login("xiaochuang", "xiaochuang is handsome");
    }
}

当,上面的代码还足以为此老多种道作进一步简化,比如将TestUtils.setupDagger();放到@Before中,或者是自从定义一个基础测试类,把TestUtils.setupDagger();推广是基础测试类的@Before里面,然后
LoginActivityTest累这个基础测试类就得了,or even
better,自定义一个JUnit Rule,在每个测试方法被调用之前自动调用
TestUtils.setupDagger();。只是这些跟眼前之主题无关,就非具体进展叙述了。后面会讲话到DaggerMock的以,这个事物可当真是神器啊!简直不用太神器!

单元测试里面,不要滥用dagger2

这边更重一下齐等同篇文章的语句,单元测试的时不要滥用dagger2,虽然现在咱们的app是因此dagger2架构起的,所有的Dependency都是当Module里面生产,但连无意味着我们以做单元测试的时刻,这些Dependency也不得不在Module里面生产。比如说,LoginPresenter

public class LoginPresenter {
    private final UserManager mUserManager;
    private final PasswordValidator mPasswordValidator;

    @Inject
    public LoginPresenter(UserManager userManager, PasswordValidator passwordValidator) {
        this.mUserManager = userManager;
        this.mPasswordValidator = passwordValidator;
    }

    public void login(String username, String password) {
        if (username == null || username.length() == 0) return;
        if (mPasswordValidator.verifyPassword(password)) return;

        mUserManager.performLogin(username, password);
    }
}

咱俩而测量的凡,LoginPresenter#login()调用了
mUserManager.performLogin()。在此,我们得以依照上面的思路,使用dagger2来mock
UserManager,做法是mock module
provideUserManager()措施,让其回到一个mock的
UserManager,然后去verify这个mock
UserManagerperformLogin()艺术赢得了调用,代码大致如下:

@RunWith(RobolectricGradleTestRunner.class)
@Config(constants = BuildConfig.class, sdk = 21)
public class LoginPresenterTest {

    @Test
    public void testLogin() throws Exception {
        TestUtils.setupDagger();
        UserManager mockUserManager = mock(UserManager.class);
        Mockito.when(TestUtils.appModule.provideUserManager(any(SharedPreferences.class), any(UserApiService.class))).thenReturn(mockUserManager);

        LoginPresenter presenter = ComponentHolder.getAppComponent().loginPresenter();
        presenter.login("xiaochuang", "xiaochuang is handsome");

        verify(mockUserManager).performLogin("xiaochuang", "xiaochuang is handsome");
    }
}

如此这般虽然可,而且为未为难,但归根结底路绕的出硌多,而且你恐怕要召开额外的局部工作,比如当AppComponent里加一个正经代码不肯定会因此的
loginPresenter();计,另外为AppModule中间来安卓系的代码,我们还必须采用Robolectric,导致测试跑起慢了成千上万。其实我们了可以不用dagger2,有重好之计,那就算是直new
LoginPresenter,传入mock UserManager

public class LoginPresenterTest {
    @Test
    public void testLogin() {
        UserManager mockUserManager = mock(UserManager.class);
        LoginPresenter presenter = new LoginPresenter(mockUserManager, new PasswordValidator()); //因为这里我们不verify PasswordValidator,所以不需要mock这个。

        presenter.login("xiaochuang", "xiaochuang is handsome");

        verify(mockUserManager).performLogin("xiaochuang", "xiaochuang is handsome");
    }
}

程序是休是简简单单多了,也爱懂多矣?
那么现在题材来了,如果这样的话,单元测试的时光,哪些情形应当为此dagger2,那些状并非为?答案是,能无用dagger2,就甭dagger2,不得已用dagger2,才故dagger2。当然,这是同一句子废话,前面我们既明朗感受及了,在单元测试里面用dagger2比并非dagger2要麻烦多了,能毫无自绝不。那么问题即使改成了,什么动静下得用dagger2、而什么时可以毫不吧?答案是,如果给测类(比如说LoginActivity)的Dependency(LoginPresenter)是通过
field injection
inject进去的,那么重测者看似(LoginActivity)的时候,就务须用dagger2,不然可怜不便优雅的管mock传进。相反,如果给测类有Constructor(比如说LoginPresenter),Dependency是通过Constructor传进的,那么就是得不动dagger2,而是径直new对象下测。这也是干吗我于眼前同首文章里肯定的推介
Constructor Injection的由来。

小结

立刻篇稿子介绍了dagger2的使用,以及当单元测试里面的应用。哦好像忘记了介绍将dagger2加到项目里面的方,其实生简短,把以下代码加入build.gradle:

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
    }
}

apply plugin: 'com.android.application'  //这个已经有了,这里只是想说明要把android-apt这个plugin放到这个的后面。
apply plugin: 'com.neenbedankt.android-apt'

dependencies {
    //other dependencies

    //Dagger2
    compile 'com.google.dagger:dagger:2.0.2'
    compile 'javax.annotation:jsr250-api:1.0'
    apt 'com.google.dagger:dagger-compiler:2.0.2'
}

应当说,DI是平种很好之模式,哪怕不做单元测试,DI也会于我们的app的架变得到底多,可读性、维护性和可拓展性强博,只不过单元测试让DI的必要性变得更为明显和亟待解决而已。而dagger2的图,或者说角色,在于其让咱们形容规范代码的时节利用DI变得容易如反掌,程序及其简洁优雅可读性强。同时,它于好几情况下受本来老大不便测的代码变得用于测试。

文中的代码在github这个类型里。

最后,如果您对安卓单元测试感兴趣,欢迎加入我们的交流群,因为许多成员过100人口,没办法扫码加入,请关注人世公众号取加入方法。

参考:https://www.youtube.com/watch?v=oK\_XtfXPkqw