motacaplaのめう

日頃得られた知見と気付きをdumpするところ

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クラス単体をテストできる。

以下が参考になった。

qiita.com

qiita.com

Cons.

  • テストの入力範囲が曖昧になる
  • テストの入力パターンが膨大になる
  • 引数が膨大になるので、修正する場合に大変 (解決策: DIコンテナを使う)

qiita.com

DIコンテナをよく分からずに使うと サービスロケータパターン になるらしい

DIコンテナ, サービスロケータパターンはまた別の機会で

興味深い議論

以下より抜粋。

依存性注入(DI)は成功したか?

"TDD が開始になったとき、論じられた最初の問題の1つが、「テストできるようにするためにコードを変更すべきか」でした。コードの可視度を変えるべきでしょうか。コードのデザインを変えるべきでしょうか。これにより、テスト可能なコードとOOカプセル化の間に衝突が生じているのです。テストを可能にするために、デベロッパがコードの密閉部をさらし始めたのです。最初は密閉フィールドやメソッドでしたが、今ではデザイン全部をさらしているのです。"

まとめ

いかがでしたか?

DIを使う場面も分かったのではないでしょうか!?

(とりあえず競プロのライブラリ整備するところから、言語に慣れるの始めたい...)

TODO: DIコンテナ, サービスロケータパターンの勉強

おわり!

References

猿でも分かる! Dependency Injection: 依存性の注入 - Qiita

DI・DIコンテナ、ちゃんと理解出来てる・・? - Qiita

僕がDIを否定する理由 - kbigwheelのプログラミング・ソフトウェア技術系ブログ