DIの考え方とandroidアプリでのサンプル(Dagger)

DI(Dependency Injection)について

たまにDI不要論とかがネットで話題になったりしてて,ふわっとDIの考え方は知ってたんだけど実際にDIパターンみたいな実装をしたことなかったのでちょっと触ってみた.

DIとは

日本語だと依存性注入みたいな小難しい訳になってしまうんだけど,概念自体は難しくはないと思う.
よく車の例で紹介されてて,例えば車クラスには( Aエンジンオブジェクト と Aタイヤオブジェクト )を属性に持ってるとする
この車クラスのエンジン部分をBエンジンオブジェクトにごっそり切り替えようとした場合に車クラスにはAエンジンに依存した実装があるので置き換えるのが大変だ!! という問題が発生する

public class Car{
  public A_Engine aEngine;
  public A_Tire aTire;
  public go(){
    aEngine.start(); // <- A_Engineの実装に依存してる!!
    // todo
  }
 public stop(){
   aEngine.stop(); // <- A_Engineの実装に依存してる!!
    // todo
 }
}

こういう問題を解決するのがDIというデザインパターンになる.
上記の問題はCarオブジェクトが具体的なオブジェクト(A_EngineやA_Break)を属性としてもっていることが原因なので,

  • 属性にはインターフェースを持たせる
  • 実装はインスタンス作成時に外部から渡す

という実装に変えることで実装の依存性をなくそうとするのがDIというデザインパターンになる.

public interface Engine { //別でinterfaceを定義する
  void start();
  void stop();
}
public class Car{
  public Engine engine;
  public Tire tire;
  public Car(){
  // コンストラクタでゴニョゴニョしてengine,tireに実装を注入する
  }
  public go(){
    engine.start(); // <- Engineはインターフェースなので依存性がなくなった
    // todo
  }
 public stop(){
   engine.stop(); // <- Engineはインターフェースなので依存性がなくなった
    // todo
 }
}

もうすこし詳しい解説は以下のリンクがわかりやすい気がする

DI:依存性の注入とは何か?
猿でも分かる! Dependency Injection: 依存性の注入

androidでのDIのサンプル

Daggerとは

squareっていうweb決済サービスがオープンソースで提供してるJava用にDIフレームワーク(android以外でも使える)

あとめっちゃ速いらしい
Android用DIコンテナDaggerはGuiceの数倍速い

準備

まずライブラリのjarを入手する.
まず, 公式ドキュメント(square)からdagger-x.x.x.jarとdagger-compiler-x.x.x.jar(2014/1/11現在 1.2.0が最新)をダウンロードする
そしてsquare/dagger(レポジトリ)のリンクからjavawriter-2.3.1.jarとjavax.inject.jarをダウンロードする(Downloadの一番下にリンクがあります)

任意のandroidプロジェクトにlibsフォルダをつくりダウンロードしてきたjarをぶちこみ,各ビルドシステムにしたがって外部ライブラリの設定を書く gradleなら多分以下の様な感じのものをbuild.gradleに書き込む

    compile 'com.android.support:appcompat-v7:+'
    compile files('libs/dagger-1.2.0.jar')
    compile files('libs/dagger-compiler-1.2.0.jar')
    compile files('libs/javawriter-2.3.1.jar')
    compile files('libs/javax.inject.jar')

目標

Daggerを使ってDebuggerという文字列を出力するインターフェースで文字列を出力してみる
今回はLogCatを使って出力するクラスとToastを使って出力するクラスを実装した

実装

まずDebuggerというインターフェースを作る showメソッドによって文字列の出力を想定する

public interface Debugger {
    void show(String text);
}

次にDebuggerを継承しLogCatで文字列を出力するLogcatDebuggerを作成する

public class LogcatDebugger implements Debugger {
    @Override
    public void show(String text) {
        Log.v("debug",text);
    }
}

同じくDebuggerを継承しToastで文字列を出力するToastDebuggerを作成する

public class ToastDebugger implements Debugger {
    Context context;
    public ToastDebugger(Context context){
        this.context = context;
    }
    @Override
    public void show(String text) {
        Toast.makeText(context,text,Toast.LENGTH_SHORT).show();
    }
}

次に設定ファイルに当たるModuleを作成する(DebuggerModule)
モジュールにあたるクラスではいくつか規則があり,まずModuleアノテーションでインジェクションするクラスを指定する.今回はMainActivity上で実行するのでMainActivityを指定する. そしてInjectして何を注入するか決めるために[provide+(型名)]の命名規則のメソッドを作成し実装をリターンする.

@Module(injects = MainActivity.class)
public class DebuggerModule {
    Context context;
    public DebuggerModule(Context context){
        this.context = context;
    }
    @Provides
    Debugger provideDebugger() {
        return new ToastDebugger(context);
    }
}
実行するクラス(MainActivity)

注入したい変数に@Injectをつけ objectGraph.inject(this);すると依存が自動で注入される.

public class MainActivity extends ActionBarActivity {

    @Inject Debugger debugger;

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

        ObjectGraph objectGraph = ObjectGraph.create(new DebuggerModule(this));
        objectGraph.inject(this);
        debugger.show("hogepiyo!!");
    }
}

Daggerにはもっと色々な機能があるけど,多分これが一番シンプルな構成だと思う.
レポジトリ上げといたので良かったら参考にしてください. (.gitignoreの設定おかしいかもしれない )

kazy1991/dagger_sample