〇前置き
Dagger2難解ですね。
導入がきつかったので、色々なサイトを手掛かりに、とりあえず簡単に導入部分をまとめました。
このブログでは@Inject,@Module,@Provide,@Componentを利用し、
依存性を注入して機能を切り替えるところまで、できる限りわかりやすく書きたいと思います。
今回はわけあってJavaにしました。
〇Dagger2って何?
googleが開発している「依存性注入のためのコンパイル時フレームワーク」
簡単に言うとクラスの中で他クラスのインスタンスを生成とかしてしまうと、
1つ1つのクラスのテストが難しくなるので、
依存性注入というコンパイル時にインスタンスを生成しておいて注入するという方法に切り替えて、
メンテナンスやテストしやすくしようというもの。
〇参考URL
・[公式]GitHub
https://github.com/google/dagger
・[公式]User's Guide
https://google.github.io/dagger/users-guide.html
・とてつもなくわかりやすいdagger2(2.11)入門
- とてつもなくわかりやすいです!
https://qiita.com/m-dove/items/767c4bfaeee53caefc4d
・これでわかる(わかったつもりになる)! Dagger 2
- アノテーションを分かりやすくまとめてあります。
https://qiita.com/kikuchy/items/a96809d621845dead8d2
・要するに DI って何なのという話
- Javaではなかったのですが、こちらの例をとても参考にさせて頂きました。
https://nekogata.hatenablog.com/entry/2014/02/13/073043
〇プログラム
・GitHub一覧
[1]Dagger2使用前のサンプル
[2]@Inject,@Componentを使用して依存注入したサンプル
[3]@Module,@Provideを使用して注入内容を切り替えるサンプル
[1]Dagger2使用前のサンプル
単純なサンプルです。
MainActivityでFortuneMachineのインスタンスを生成。
するとFortuneMachineのコンストラクタでTwiterClientのインスタンスが生成される。
FortuneMachineのcheckFortuneを呼び出すと、ランダムにおみくじを引いて、
TwitterClientのpostDataでTwitterに投稿。(Twitter投稿部分は端折っており、ログ吐くだけ)
念のため、文字列もMainActivityに返しています。
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
FortuneMachine fortuneMachine = new FortuneMachine();
Log.w( "DEBUG_DATA", "result = " + fortuneMachine.checkFortune());
}
}
public class FortuneMachine {
TwiterClient twiterClient;
String[] fortunes = {"大吉","中吉","小吉","凶","大凶"};
public FortuneMachine(){
// TwiterClientのインスタンスを生成
twiterClient = new TwiterClient();
}
public String checkFortune(){
int no = getRandomNo();
twiterClient.postData(fortunes[no]);
return fortunes[no];
}
public int getRandomNo(){
Random r = new Random();
int n = r.nextInt(fortunes.length);
return n;
}
}
public class TwiterClient {
public TwiterClient(){
}
public boolean postData(String fortune){
// Twitterに通信処理
Log.w("DEBUG_DATA","postData " + fortune);
return true;
}
}
このプログラムですとFortuneMachineをテストするたびにTwitterClientによってデータが投稿されてしまったり、拡張時に気を遣うことになります。
そこでDagger2を使用してFortuneMachineにTwitterClientを注入してみます。
[2]@Inject,@Componentを使用して依存注入したサンプル
implementation "com.google.dagger:dagger:2.11"
implementation "com.google.dagger:dagger-android-support:2.11"
annotationProcessor "com.google.dagger:dagger-android-processor:2.11"
annotationProcessor "com.google.dagger:dagger-compiler:2.11"
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//FortuneMachine fortuneMachine = new FortuneMachine();
Machine fortuneMachine = DaggerMainActivity_Machine.create();
String result = fortuneMachine.maker().checkFortune();
Log.w( "DEBUG_DATA", "result = " + result);
}
// Dagger2で依存するオブジェクトを記載
@Component
interface Machine{
FortuneMachine maker();
}
}
public class FortuneMachine {
// これでNewしているのと同じ扱い
@Inject
TwiterClient twiterClient;
String[] fortunes = {"大吉","中吉","小吉","凶","大凶"};
// コンストラクタにも必要
@Inject
public FortuneMachine(){
// twiterClient = new TwiterClient();
}
public String checkFortune(){
int no = getRandomNo();
twiterClient.postData(fortunes[no]);
return fortunes[no];
}
public int getRandomNo(){
Random r = new Random();
int n = r.nextInt(fortunes.length);
return n;
}
}
public class TwitterClient {
// コンストラクタにも必要
@Inject
public TwitterClient(){
}
public boolean postData(String fortune){
// Twitterに通信処理
Log.w("DEBUG_DATA","postData " + fortune);
return true;
}
}
・変更点1
Dagger2を使うためにbuild.gradleに設定追加。
・変更点2
FortuneMachineでTwiterClientのインスタンスを生成している部分が、
-------------------------------
@Inject
TwiterClient twitter
-------------------------------
になっている。
またFortuneMachineとTwitterClientのコンストラクタにも@Injectが付いています。
依存注入して作成したいインスタンスの変数と管理下に置かれるコンストラクタに付けるルールです。(もうこの時点で複雑なルール)
・変更点3
MainActivityに
-------------------------------
@Component
interface Machine{
FortuneMachine maker();
}
-------------------------------
が追加されているのと、
インスタンスの生成が
-------------------------------
Machine fortuneMachine = DaggerMainActivity_Machine.create();
-------------------------------
に変わっています。
@ComponentはDagger2管理下に置く依存関係をまとめて置くところです。(総合管理的なところ?)
maker()は自由に変えてよいですが、書式は覚えるしかないかと。
これを書いたら一旦コンパイルすると、
DaggerMainActivity_Machine implements MainActivity.Machine
というクラスが書いてあるファイルが出現します。(詳細は参考URLにある[とてもつもなくわかりやすいdagger2(2.11)入門]に詳しく書いてあります。)
自動生成されるDagger[クラス名]_[インターフェース名]という名前の規則も覚えるしかないですね・・・
そのインスタンスを取得するためには.create()を付けて取得します。
これで、FortuneMachineの中でTwitterClientがnewされずに、
外部でインスタンス化されたものがFortuneMachineに注入されていることになっています。
ここまでやって分かると思いますが、すっごい面倒な割にメリットがほぼないですね!!
ですが、これから発展させていくと便利になると思って勉強していきましょう![]()
[3]@Module,@Provideを使用して注入内容を切り替えるサンプル
やりたいことは、FortuneMachineをいじらずに、注入するものを操作して、TwitterにアップするかFacebookにアップするか設定できるよ、ということです。
最初に簡単に書くと、Interfaceを作成して実装したものをいくつか作成。
その内容をFortuneModuelというクラスで管理して、総合管理の@Componentさんにも教えてあげましょうということです。
同上
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//FortuneMachine fortuneMachine = new FortuneMachine();
Machine fortuneMachine;
// fortuneMachine = DaggerMainActivity_Machine.create();
fortuneMachine = DaggerMainActivity_Machine.builder()
.fortuneModule(new FortuneModule())
.build();
String result = fortuneMachine.maker().checkFortune();
Log.w( "DEBUG_DATA", "result = " + result);
}
// Dagger2で依存するオブジェクトを記載
// 使用するモジュールを記載
@Component(modules = FortuneModule.class)
interface Machine{
FortuneMachine maker();
}
}
public class FortuneMachine {
// これでNewしているのと同じ扱い
@Inject
Client clinet;
String[] fortunes = {"大吉","中吉","小吉","凶","大凶"};
// コンストラクタにも必要
@Inject
public FortuneMachine(){
}
public String checkFortune(){
int no = getRandomNo();
clinet.postData(fortunes[no]);
return fortunes[no];
}
public int getRandomNo(){
Random r = new Random();
int n = r.nextInt(fortunes.length);
return n;
}
}
public interface Client {
boolean postData(String fortune);
}
public class TwitterClient implements Client{
// コンストラクタにも必要
@Inject
public TwitterClient(){
}
public boolean postData(String fortune){
// Twitterに通信処理
Log.w("DEBUG_DATA","postTwitter " + fortune);
return true;
}
}
public class FacebookClient implements Client {
// コンストラクタにも必要
@Inject
public FacebookClient(){
}
public boolean postData(String fortune){
// Facebookに通信処理
Log.w("DEBUG_DATA","postFacebook" + fortune);
return true;
}
}
// 注入されるものを提供するクラス
// ...Moduleという名前にする
@Module
public class FortuneModule {
// 注入されるものを提供する
// provide...という名前にする
@Provides
static Client provideClient(){
return new FacebookClient();
}
}
・変更点1
public interface Client を作成。
これをTwitterClientとFacebookClientが継承して、
FortuneMachineの@InjectをClinetで定義して値を受け取ることができます。
・変更点2
public class FortuneModuleを作成。
@Providesは@Injectに提供するものを管理する関数に付ける。
@Moduleは@Providesをまとめるクラスに付ける。
参考URLにある[これでわかる(わかったつもりになる)! Dagger 2]の
----------------------------------------
@Inject が車の給油口だとしたら、 @Module はガソリンスタンド、 @Provides はスタンドに設置されている給油機にあたる感じ。
----------------------------------------
という例えは秀逸ですね。
・変更点3
MainActivityの@Componentにモジュールの設定が追加された。
----------------------------------------
@Component(modules = FortuneModule.class)
----------------------------------------
また、DaggerMainActivity_Machineのインスタンスの生成方法が、モジュール指定バージョンに変更になった。
----------------------------------------
fortuneMachine = DaggerMainActivity_Machine.builder()
.fortuneModule(new FortuneModule())
.build();
----------------------------------------
さりげなくfortuneModuleという関数があるけど、自動生成されているんだろうと。
これで、FoturneModuleの値を変更するだけで、
どのClientを使うか、もしくはMocを設定してテストをしたりなどができます。
〇まとめ
ハッキリ言ってお作法や謎ルールが多すぎる。
この程度のソースでは使って得なことがほぼない。
インスタンスの切り替えぐらいif文で切り替えたらいいんじゃない!?
って思ってしまいますが、GoogleのサンプルソースにはDaggerが付いてくるわけで理解しないわけにはいかないんですよね・・・
ViewModelとかに利用すると便利なようなのでそれはまた今度。