かとじゅんの技術日誌

技術の話をするところ

簡単にDIってどんなもの?何がよいの?

DIについて社内で理解してもらう機会があったのでここで簡単におさらい。

まず簡単な学生の試験の点数を評価してくれるインターフェイスと実装クラスを例に説明します。

// 試験点数の評価ロジックのインターフェイス
public interface CheckPointLogic {
  public String check(int point);
}

// Aパターンの試験点数の評価の実装
public class ACheckPointLogicImpl implements CheckPointLogic {
  public String check(int point){
    if ( point == 100 ){
      return "Good!";
    }else{
      return "Bad!!";
    }
  }
}

// 評価ロジックを実際に使います
public class Sample {
  public static void main(String[] args){
    CheckPointLogic checkPointLogic = new ACheckPointLogicImpl();
    String result = checkPointLogic.check(80);
    System.out.println(result);
  }
}

ACheckPointLogicImplは、100点以外は悪い評価とかなり厳しい評価基準です。これはやりぎなので別の実装 BCheckPointLogicImplを作りました。

// Bパターンの試験点数の評価の実装
public class BCheckPointLogicImpl implements CheckPointLogic {
  public String check(int point){
    if ( point >= 90 ){
      return "Good!";
    } else if ( point >= 50 ){
      return "So So, ...";
    } else {
      return "Bad!!";
    }
  }
}

// 評価ロジックを実際に使います
public class Sample {
  public static void main(String[] args){
    CheckPointLogic checkPointLogic = new BCheckPointLogicImpl();
    String result = checkPointLogic.check(80);
    System.out.println(result);
  }
}

BCheckPointLogicImplは、かなり現実的な評価基準に変わりました。ここで注意するのは評価ロジックを使っているSampleクラスのほうです。前のものと比べてどうでしょうか?
そうです。インターフェイスを変えずに実装クラスだけを差し替えました。
今回のアプリではBCheckPointLogicImplが採用されることになりましたが、あるケースによってはACheckPointLogicImplを使う場合も出てくるかと思います。その場合はCheckPointLogicFactoryクラスなるものを作って対応することになるかと思いますが、ファクトリの中ではACheckPointLogicImpl、BCheckPointLogicImplをnewするコードはどうしても密に結合することになってしまいます。
この密結合をDIコンテナを使って疎結合に変えてみましょう。

// 評価ロジックを実際に使います
public class Sample {
  private static String PATH = "app.dicon";
  public static void main(String[] args){
    S2Container container = S2ContainerFactory.create(PATH);
    CheckPointLogic checkPointLogic = (CheckPointLogic)container.getComponent("checkPointLogic");
    String result = checkPointLogic.check(80);
    System.out.println(result);
  }
}

app.dicon

<?xml version="1.0" encoding="Shift_JIS"?>
<!DOCTYPE components PUBLIC "-//SEASAR//DTD S2Container//EN"
"http://www.seasar.org/dtd/components.dtd">
<components>
    <component name= "checkPointLogic" class= "BCheckPointLogicImpl">
    </component>
</components>

Sampleクラスを見るとどこにも実装クラスをnewしているコードがありません。これで依存関係は切れている状態になります。
(でも、DIコンテナには依存しとるやんけ!という突っ込みはありますが、これも極力container.getComponentしない方法も可能です。これは後に説明します。まずは基本的に使い方として理解してもらえればと思います)

CheckPointLogic checkPointLogic = (CheckPointLogic)container.getComponent("checkPointLogic");

実際この部分でどのクラスのインスタンスが生成されるかは、上記のapp.diconで決まります。
getComponentの引数で指定したコンポーネント名 checkPointLogic に対応するクラスは、BCheckPointLogicImplクラスであると設定されていますので、実際にはBCheckPointLogicImplクラスのインスタンスDIコンテナ経由で返されます。
ある日突然上司から言われてロジックを変えなくてはならなくなった場合は、app.diconのBCheckPointLogicImplをACheckPointLogicImplに変えるだけで修正範囲を最小限にできます。Sampleクラスの変更は不要です。

Dependency Injection = 依存性注入と言われる所以ですが、
開発中は各クラスの依存関係をインターフェイスによって分離して実装クラスをnewするコードも排除。
実行時にはクラス間の依存関係をアプリケーション外部から注入するというイメージなります。
外から依存関係を注入するからDIなんですね。

DIでは、インターフェイスだけ知っていればよくあとの実装クラスはDIの設定に応じてDIコンテナが自動的に引き合わせてくれます。

DIによってテストしやすくなるといろいろメリットありますが、動的にアプリケーションの構成を変えれるのは大きなメリットだと思います。たとえば、標準品を作っても特定顧客向けのコードを織り込んだりしているうちにスパゲッチになったという話はよく聞きます。最初から見えない顧客を想定しても仕方なので、そういうときはその顧客の仕様が決まった時点で顧客用の実装クラスを作ってインジェクしてあげればよいのではないかと思います。この際に、変更・追加が必要なのは顧客用実装クラスとDIの設定ファイルだけです。基幹部分のコードは変更の必要がなくなります。

まだまだ説明することがあるのですが、まずは第一弾、ご参考までに。

参考書籍はと、

Seasar入門 はじめてのDI&AOP

Seasar入門 はじめてのDI&AOP

  • 作者: 須賀幸次,木村聡,西川麗,高安厚思,白井博章,椎野峻輔,岡薫,藤村浩士,ひがやすを
  • 出版社/メーカー: ソフトバンククリエイティブ
  • 発売日: 2006/02/25
  • メディア: 大型本
  • 購入: 7人 クリック: 51回
  • この商品を含むブログ (67件) を見る