My開発メモ

依存性の注入、あるいは依存性の逆転の例

依存性の注入について、簡単な例を考えてみた。

(1) 普通の場合

HeroクラスとWeaponクラスを例に考えてみる。

以下のようなクラスを考える。

Hero.java (charactorパッケージ)
package charactor;

import weapon.Sword;
import weapon.Weapon;

public class Hero {
  private String name;
  private int hp;
  private Weapon weapon = new Sword();
  
  public Hero() {
    this("ミナト", 100);
  }
  public Hero(String name, int hp) {
    this.name = name;
    this.hp = hp;
  }
  public String getName() {
    return name;
  }
  public int getHp() {
    return hp;
  }
  public void useWeapon() {
    System.out.print(name + "は ");
    weapon.use();
  }
}
Weapon.java (weaponパッケージ)
package weapon;

public interface Weapon {
  void use();
}
Sword.java (weaponパッケージ)
package weapon;

public class Sword implements Weapon {
  private String name;

  public String getName() {
    return name;
  }

  public Sword() {
    this.name = "炎の剣";
  }

  @Override
  public void use() {
    System.out.println(name + "を使って切りつけた");
  }
}
Main.java (mainパッケージ)
package main;

import charactor.Hero;

public class Main {
  public static void main(String[] args) throws Exception {
    Hero h = new Hero();
    h.useWeapon();
  }
}

依存性に着目すると、

(1) Mainクラスは、その中で Heroクラスのインスタンスを生成しているので、
「Heroクラスに依存している」と言える。

(2) Heroクラスは、その中で Swordクラスのインスタンスを生成しているので、
「Swordクラスに依存している」のだが、Weaponインターフェースを介在
させているので(ポリモーフィズム)、疎結合と言える。

(2) 依存性の注入(逆転)

まず、次の annotationを定義する。

Resource.java (annotationパッケージ)
package annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Resource {
  Class value();
}

@Target(ElementType.FIELD) — このアノテーションがフィールド宣言の修飾子としてのみ記述できることを示す。

@Retention(RetentionPolicy.RUNTIME) — 注釈はコンパイラによってクラス・ファイルに記録され、実行時にVMによって保持されます。このため、リフレクト的に読み取ることができます。
(「列挙型RetentionPolicy」https://docs.oracle.com/javase/jp/8/docs/api/java/lang/annotation/RetentionPolicy.html#RUNTIME)

Class value() — この記述によって、アノテーション記述子 @Resource(引数) の「引数」に classリテラル を指定できる。

この @Resource 記述子を Heroクラスに記述する。

Hero.java (charactorパッケージ)
package charactor;

import annotation.Resource;
import weapon.Sword;
import weapon.Weapon;

public class Hero {
  private String name;
  private int hp;
  @Resource(Sword.class)      // ここにアノテーションを記述する
  private Weapon weapon;      // インスタンス生成は、はずす
  
  public Hero() {
    this("ミナト", 100);
  }
  public Hero(String name, int hp) {
    this.name = name;
    this.hp = hp;
  }
  public String getName() {
    return name;
  }
  public int getHp() {
    return hp;
  }
  public void useWeapon() {
    weapon.use();
  }
}

インスタンスを生成して、必要なクラスのフィールドにセットする役割のクラスを用意する。

Container.java (coreパッケージ)
package core;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;

import annotation.Resource;

public class Container {

  public Object getInstance(Class clazz) throws Exception {   (1)
    Object obj = clazz.getConstructor().newInstance();        (2)
    
    Field[] fields = clazz.getDeclaredFields();               (3)
    for (Field f : fields) {
      Annotation[] annotations = f.getAnnotations();          (4)
      for (Annotation an : annotations) {                     
        if (an instanceof Resource) {                         (5)
          Resource resource = (Resource) an;                  (6)
          Class target = resource.value();                    (7)
          f.setAccessible(true);                              (8)
          f.set(obj, target.getConstructor().newInstance());  (9)
        }
      }
    }
    return obj;
  }
}

(1) — 引数の Class clazz は、フィールドに @Resource をつけたクラス・リテラルを指定する。今回の場合は Hero.class。

(2) — 今回の場合、Heroクラス情報のコンストラクタの newInstance()メソッドで、Heroクラスのインスタンスを生成する。それを Object型に代入する。

(3) — クラスのフィールド情報を配列で取得している。今回の場合だと、Heroクラスには、String型のname、int型のhp、Weapon型のweapon の3つである。

(4) — それぞれのフィールドのアノテーションを配列で取得している。とは言っても、今回だとアノテーションは一つしかない。

(5) — 各アノテーションを調べて、そのアノテーションが Resourceならば、

(6) — Resourceアノテーションの引数に記述されたクラス・リテラルを取得して target とする。今回の場合は、Sword.class である。

(7) — 今から Hero.class のフィールド”weapon” に変更を加えるので、変更を可能にしている。

(8) — そのフィールドをもつオブジェクトについて、そのフィールドに targetのインスタンスをセットする。今回の場合は、Heroインスタンスの weaponフィールドに Sword.class のインスタンスを生成してセットしている。

Main.java (mainパッケージ)
package main;

import charactor.Hero;
import core.Container;

public class Main {

  public static void main(String[] args) throws Exception {
    Container con = new Container();
    Hero h = (Hero) con.getInstance(Hero.class);
    h.useWeapon();
  }
}

Containerクラスの getInstance()メソッドに Hero.class リテラルを渡したら、Heroクラスの Weaponフィールドに @Resource で指定したクラス(Swordクラス) がセットされ、Object型になって返却される。それを Hero型にキャストしている。

カテゴリー: Java, memo, Spring

タグ: annotation, Container, Dependency Injection, Di, アノテーション, コンテナ, 依存性の注入, 依存性の逆転

カウント: 123