依存性注入(DI: Dependency Injection)と Dagger 2
依存性注入(DI: Dependency Injection)という単語を知ってはいたけれど、その意味を知らなかったので調べた。また、前に書いた Todo-Android という Android アプリを元に依存性注入を考えた。
Android では、Dagger というライブラリが DI を実装するのに有名みたいだ。Square 製の Dagger があり、それをフォークした Google 製の Dagger 2 がある。開発が盛んである Dagger 2 を使った。
依存性注入すると何が解決できそうか
Todo-Android アプリの中では、RealmTodoManager が Todo 情報を管理していて、Todo リストを返したり、Todo の追加・更新・削除を担っている。
public class RealmTodoManager {
public RealmTodoManager() { /* 省略 */ }
public Todo find(int id) { /* 省略 */ }
public List<Todo> findAll() { /* 省略 */ }
public void insert(String name, boolean completed) { /* 省略 */ }
public void update(int id, String name, boolean completed) { /* 省略 */ }
public void delete(int id) { /* 省略 */ }
}
この RealmTodoManager は、各 Activity でインスタンス化し、使い回している。各 Actiivty は、RealmTodoManager に依存している状態である。
ここで開発の途中にデータ管理を Realm から SQLite に変えた時は、SQLiteTodoManager を作成することになるが、各 Activity を全部書き直すことになる。あるいは、Debug ビルドの時は、モックデータを返すようにしたい時に、どのように実装すれば良いのか悩むことになる。
また、RealmTodoManager をシングルトンにしたい時に各 Activity で書き換えが必要になるし、シングルトンはテストがしにくいから、なるべくなら避けたい気がする。
依存性注入を行うと、このようなもやもやがうまく解消できるようだ。
Dagger 2 による依存性注入の手順
Gradle 設定
トップレベルの build.gradle ファイル(Project)に、android-apt の設定を追加する。
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:1.1.0'
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.4' // 追加
}
}
/* 省略 */
アプリケーションの build.gradle ファイル(Module: app)に、以下の設定を追加する。
apply plugin: 'com.android.application'
apply plugin: 'com.neenbedankt.android-apt' // 追加
/* 省略 */
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'javax.annotation:jsr250-api:1.0' // 追加
compile 'com.google.dagger:dagger:2.0.1' // 追加
apt 'com.google.dagger:dagger-compiler:2.0.1' // 追加
}
実装
まずは、TodoManager というインターフェースを宣言する。
public interface TodoManager {
Todo find(int id);
List<Todo> findAll();
void insert(String name, boolean completed);
void update(int id, String name, boolean completed);
void delete(int id);
}
このインターフェースを実装した RealmTodoManager を作成する。今後、SQLiteTodoManager を作るときもこのインターフェースを実装する。同様にモックデータを返す MockTodoManager とかも作れる。
public class RealmTodoManager implements TodoManager {
public RealmTodoManager(Context context) { /* 省略 */ }
@Override Todo find(int id) { /* 省略 */ }
/* 省略 */
}
モジュールを作成する。モジュールは、インスタンスを提供(provide)するメソッド群を定義したものである。メソッド名には provide
を接頭語にする、メソッドは @Provides
アノテーションを宣言するのが決まりとなっている。
provideTodoManager()
は、TodoManager を提供する関数。この返り値の型は、TodoManager としているが、実際には RealmTodoManager のインスタンスを返している。今後 SQLiteTodoManager に差し替わった時には、ここの記述を差し替えるだけで良くなる。シングルトンにするかどうかも @Singleton
アノテーションにより宣言できる。RealmTodoManager 側でシングルトンの設定をする必要がなくなるため、テストコードが書きやすくなる。
@Module
public class PersistenceModule {
@Provides @Singleton
public TodoManager provideTodoManager() {
return new RealmTodoManager();
}
}
次にコンポーネントを作成する。これは依存性を注入する先と、モジュールの関係性を定義する。ここでは、PersistenceModule が MainActivity に注入できるように宣言する。modules
と複数形になっていることから察せられるように、複数のモジュールを紐付けることができる。
@Singleton
@Component(modules = PersistenceModule.class)
interface PersistenceComponent {
void inject(MainActivity activity);
}
実際に利用するには、MainActivity の onCreate()
内などで inject
する。この時に、@Inject
と書かれたメンバ変数に provide
で記述されたものが注入される。これを「依存性が注入された」と言うみたいだ。ポイントとしては、注入する実態が MainActivity 側から取り除かれているということ。
public class MainActivity extends BaseActivity {
@Inject TodoManager mTodoManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
PersistenceComponent component = DaggerPersistenceComponent.create();
component.inject(this);
}
}
ちなみに DaggerPersistenceComponent だが、これは PersistenceComponent のビルド後に作成される。