どうして Dagger を利用するのか、同じことは setter を実装して外部から利用するインスタンスを変更することで実現できるのではないのか?
結論から言うと Dagger を利用した依存性の解決(注入)を実現することが楽だからです。
そもそも、広い意味での DI の実現方法には setter を利用した DI が存在します。setter injection と言うそうです。
以前の勉強会のコード を setter injection を利用して実装すると、だいたい次のようになると思います。
public class MainActivity extends Activity {
private Today today;
public void setToday(Today today) {
this.today = today;
}
@Override
protected void onResume(){
Log.d("Android_Test_BC", showHour());
}
String showHour() {
return "今は" + today.getHour() + "時です";
}
}
public class CustomApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks {
@Override
public void onActivityCreated(Activity activity, Bundler savedInstanceState) {
if (activity instanceof MainActivity) {
((MainActivity) activity).setToday(new TodayImpl());
}
}
// 他のメソッドは省略
})
}
}
@RunWith(JUnit4.class)
public class MainActivityTest {
static class MockToday implements Today {
@Override
public int getHour() {
return 12;
}
}
@Test
public void showHour() throws Throwable {
final MainActivity activity = new MainActivity();
activity.setToday(new MockToday());
final String hour = activity.showHour();
assertThat(hour, is("今は12時です"));
}
}
無事にもとのコードとだいたい同じことが実現できました。ただし、このコードには問題があります。
それは、 CustomApplication
の実装です。 MainActivity
の setToday
を呼び出すために、 CustomApplication
で ActivityLifecycleCallbacks
を利用しています。 ActivityLifecycleCallbacks
の中では instanceof
演算子を利用して、 MainActivity
を判断し setToday
を呼び出しています。この実装は人の目で判断しなければならない箇所です。このサンプルでは1つの Activity に対してのみ instanceof
を利用していますが、アプリの拡張に伴って複数の Activity
や Fragment
を利用する場合は多くあります。
そうなってくると、「必要なインスタンスを set し忘れる」とか言ったミスが発生する場合があります。
Dagger2 はアノテーショを利用してコンパイル中に依存性の解決を行うコードを自動生成します。そのため、必要なインスタンスの生成やセットを忘れていたとしてもコンパイルエラーが発生して、人間によるミスが起きる可能性は低くなるといえます。
まとめると、 Dagger2 を利用するモチベーションは次の2点です
- setter injection を実現した場合、コードが増えてくるとミスが起こりがちになるため
- コードの自動生成が行われるので、人が書くべきコードの量は少なくなって、DIの実現が楽になるため
Dagger 2 is the first to implement the full stack with generated code. The guiding principle is to generate code that mimics the code that a user might have hand-written to ensure that dependency injection is as simple, traceable and performant as it can be.
とあるので、コードが自動生成される部分が大きなメリットと言えると思います。
すみません、こちらに記載した方がよかったですね。
Activityの起動(ライフサイクルでいうとonCreateから)の段階からすでにテスト用の状態に切り替わっている、という点ですね。
たしかに後からセットしてもできないことがありますね。
色々な方のサンプルで、Applicationクラスを継承したクラス(上でいうCustomApplication)にてcomponentをメンバ変数として保持し、各アクティビティでそのcomponentを利用するようなものが多く、この点も疑問でしたが、Applicationクラスのconponentの段階でテスト用のものに差し替えれば、ActivityをonCreateする段階ですでにテスト用に切り替わっている、ということが実現できそうだな、と思いました。
ありがとうございます、すっきりしました。実際もっと使って見て実感したいです。