Red > Green > Refactor > Red

cycle is based on desire

Dependency Injection

「DI パターン」と これを利用した「DI コンテナ」ってのがある

「DI コンテナ」使いたいけど「DI パターン」って何?状態 > 荒いけどまとめる

ここにあることが全部ではないので、正確なことは最下記リンクへ

DI パターンとは何か

インスタンス変数にオブジェクトを割当てること」

「或るクラスに依存するオブジェクトの作成を、他の誰かの責務にすること」

James Shore 曰く

「大げさな言い回しだ(良い意味でと付け加えておく)」

まず DI パターンが出来るまでの実情

概要

  • 一般的に或るクラスは 多数の他クラスオブジェクト (以下 "dependencies")
    を属性として持っている
  • "dependencies" は要求/仕様変更/テスト実行時に
    メンテナンス、テストによってはスタブ/モックの用意が必要
    • ...この「メンテナンス、スタブ/モック用意」
      やり易い方法がないものか?

詳細(例を交え)

  • 下サンプルの SmileCar について、 Wheel/Engine の "dependencies" は vendorABC である
  • もし、"dependencies" が vendorABC ではなく vendorXYZ になった場合
  • "dependencies" をそれぞれ vendorABCWheel(), vendorABCEngine() を変更する
    • ... が、これって本当に良いやり方?
      • メンテは面倒(影響範囲調査など、巨大クラスになれば尚更)
      • 責任範囲という面でも SmileCar(車自身) が
        "dependencies"(Wheel(タイヤ) や Engine(エンジン)) の「生成」にまで
        責務を持つべき?

(before)

(Whee/Engine がそれぞれ 抽象クラスを具象化したクラスであったり
interface を implements している前提をかなりすっ飛ばしているのは意図的)

public class SmileCar {
  private DatabaseUtil db;
  private Wheel wheel     = new vendorABCWheel();  // 仕様変更でメンテが必要になりそう
  private Engine engine   = new vendorABCEngine(); // 同上

  public Car() {}

  public showWheelNameAndEngineName() {
    try {
      // テストの時はどこの DB 見にいく?
      // getConnection() 内で 本番の時は... テストの時は...
      // って 分岐してないですよね?
      db.getConnection();

      System.out.println(wheel.getWheelName());
      System.out.println(engine.getEngineName());
    } catch(Exception e) {
      e.printStacTrace();
    }
  }
}

(after)

public class SmileCar {
  private DatabaseUtil db;
  private Wheel wheel   = new vendorXYZheel();   // vendorABC > vendorXYZ
  private Engine engine = new vendorXYZEngine(); // 同上

  public Car() {
    db = new DatabaseBase();
  }

  public showWheelNameAndEngineName() {
    try {
      // テストの時はどこの DB 見にいく?
      // getConnection() 内で 本番の時は... テストの時は...
      // って 分岐してないですよね?
      db.getConnection();

      System.out.println(wheel.getWheelName());
      System.out.println(engine.getEngineName());
    } catch(Exception e) {
      e.printStacTrace();
    }
  }
}

DI パターンの登場

やることは単純に、冒頭通り

インスタンス変数にオブジェクトを割当てること

上の SmileCar (仕様変更後) を例にする

インスタンス変数にオブジェクトを割当てる

まずは Wheel/Engine オブジェクトをインスタンス変数に割当てる

これで、Wheel/Engine の仕様変更があっても「SmileCar クラス自身」は手をつけなくていい

public class SmileCar {
  private DatabaseUtil db;
  private Wheel wheel;
  private Engine engine;

  public Car(Wheel wheel, Engine engine) {
    db = new DatabaseBase();
    this.wheel  = wheel;
    this.engine = engine;
  }

  public showWheelNameAndEngineName() {
    try {
      db.getConnection();

      System.out.println(wheel.getWheelName());
      System.out.println(engine.getEngineName());
    } catch(Exception e) {
      e.printStacTrace();
    }
  }
}

次に db オブジェクトをインスタンス変数に割当てる

クラス設計がいけてないので「え?」となるが、あくまで一例

public class SmileCar {
  private DatabaseUtil db;
  private Wheel wheel;
  private Engine engine;

  public Car(DatabaseUtil db, Wheel wheel, Engine engine) {
    this.db     = db;
    this.wheel  = wheel;
    this.engine = engine;
  }

  public showWheelNameAndEngineName() {
    try {
      db.getConnection();

      System.out.println(wheel.getWheelName());
      System.out.println(engine.getEngineName());
    } catch(Exception e) {
      e.printStacTrace();
    }
  }
}

DI 自体は終わったが、データベースモック化はどんな風に?

概略コードは以下

  • SmileCar は何もしなくて良い
  • 本番DB に繋ぐかモックしたものを作るかは MockDatabase ないの処理で決める
public MockDatabase extends DatabaseUtil {
  // モック用のフィールド、メソッド用意
}

public class SmilCarTest {
  public void TestShowWheelNameAndEngineName() {
    MockDatabase mockDatabase = new MockDatabase()
    SmileCar smileCar = new SmileCar(
      mockDatabase, new vendorXYZWheel(), new venXYZEngine()
    );
    smileCar.showWheelNameAndEngineName();
    smileCar.sometest();
  }
}

public class SmileCar {
  private DatabaseUtil db;
  private Wheel wheel;
  private Engine engine;

  public Car(DatabaseUtil db, Wheel wheel, Engine engine) {
    this.db     = db;
    this.wheel  = wheel;
    this.engine = engine;
  }

  public showWheelNameAndEngineName() {
    try {
      // (特に指定がなければ) 本番用の DB を使う
      // テスト用の DB(モック) を使う > SmileCarTest#TestShowWheelNameAndEngineName
      db.getConnection();

      System.out.println(wheel.getWheelName());
      System.out.println(engine.getEngineName());
    } catch(Exception e) {
      e.printStacTrace();
    }
  }
}

「依存性の注入」について思うこと(感想レベル)

DI[Dependency Injection] が 「依存性の注入」と訳されている

少しでも英語リソースやそこにある議論を眺めたら "dependency"

が「依存」そのものじゃなくて

「オブジェクト」やこれに近しい「モノそれ自身」だと気づくはず

引用参考リンク

http://www.jamesshore.com/Blog/Dependency-Injection-Demystified.html

http://stackoverflow.com/questions/130794/what-is-dependency-injection?rq=1