Dependency Injection (DI) について入門した話
これは何?
「Dependency Injection (DI) を知ってますか?」と聞かれ、何も分からなかったので調べてまとめました Javaでコードを書いてみたので、参考になればと思います
DIとは
DIは"あるクラスが他クラスのオブジェクトを引数で受けとる"ようなソフトウェアパターンを指します。
逆にDIでないのは"あるクラスが他クラスのオブジェクトを生成する"ようなソフトウェアパターンを指します。
これだけだと分からないので、続けてコードを書いてみましょう。
SampleCode
ここに上げました
Java-practice/src/main/java/motacapla/di at master · motacapla/Java-practice · GitHub
前提
説明のために下記のCatクラスを用意しました、可愛く鳴きます
public class Cat { private String name; public Cat(String name) { this.name = name; } public String meow() { return this.name+"meow!"; } }
DI
DIされた実装は以下になります、Catクラスの変数catを渡しております
public class diSample { private Cat cat; public diSample(Cat cat) { this.cat = cat; } public String call() { return this.cat.meow(); } }
テスト(使い方)は以下になります、newしたものを外から突っ込みます
import static org.junit.Assert.*; import org.junit.Test; public class diSampleTest { @Test public void testNotd() { Cat cat = new Cat("Mike"); diSample di = new diSample(cat); assertEquals("Mikemeow!", di.call()); } }
DIされていない
DIされていない実装です、notdiSampleクラス内でCatクラスのオブジェクトをnewしてますね!これが「依存性(Dependency)」です!
public class notdiSample { private Cat cat; public notdiSample() { this.cat = new Cat("Mike"); } public notdiSample(String s) { this.cat = new Cat(s); } public String call() { return this.cat.meow(); } }
テスト(使い方)は以下になります、直接コンストラクタ呼んじゃうよ〜
import static org.junit.Assert.*; import org.junit.Test; public class notdiSampleTest { @Test public void testDi() { notdiSample di = new notdiSample(); assertEquals("Mikemeow!", di.call()); } @Test public void testDi2() { notdiSample di = new notdiSample("nyan"); assertEquals("nyanmeow!", di.call()); } }
で、結局何が嬉しいの?
DIパターンのPros/Consをまとめました。
Pros.
- Catクラスを呼び出さずにdiSampleクラスを利用できる。
依存性がある場合はdiSampleクラスを利用するために、他クラスの実装も必要になる。
今回はそうでもないが、他クラス(上記例のCatクラス)が大規模で重い場合、メモリ消費量の増加や実行時間の増大につながる。テストをする時にはクリティカルになりえる?
- Catクラスを類似したクラスへ簡単に変えられる
例えばCatクラスを継承したBabyCatクラスを考える。これをdiSampleで利用する場合、引数でBabyCatクラスを渡すだけで良い (はず) 。
- モックを利用できる。 (モックオブジェクトを使ったユニットテストを促進)
送信メッセージ側で一時的にテスト用クラスを作り、モックとして使うことができる。
例えばロガーのモックを使うことで、ログを吐かれることなくdiSampleクラス単体をテストできる。
以下が参考になった。
Cons.
- テストの入力範囲が曖昧になる
- テストの入力パターンが膨大になる
- 引数が膨大になるので、修正する場合に大変 (解決策: DIコンテナを使う)
DIコンテナをよく分からずに使うと サービスロケータパターン になるらしい
DIコンテナ, サービスロケータパターンはまた別の機会で
興味深い議論
以下より抜粋。
"TDD が開始になったとき、論じられた最初の問題の1つが、「テストできるようにするためにコードを変更すべきか」でした。コードの可視度を変えるべきでしょうか。コードのデザインを変えるべきでしょうか。これにより、テスト可能なコードとOOカプセル化の間に衝突が生じているのです。テストを可能にするために、デベロッパがコードの密閉部をさらし始めたのです。最初は密閉フィールドやメソッドでしたが、今ではデザイン全部をさらしているのです。"
まとめ
いかがでしたか?
DIを使う場面も分かったのではないでしょうか!?
(とりあえず競プロのライブラリ整備するところから、言語に慣れるの始めたい...)
TODO: DIコンテナ, サービスロケータパターンの勉強
おわり!
References
猿でも分かる! Dependency Injection: 依存性の注入 - Qiita