こんにちは。お久しぶりです。
Springで開発中なのですが、なんとも難しい。。。
今日はDIについて書こうかなと思います。
DI(依存性注入)の定義
コンピュータプログラムのデザインパターンの一つで、オブジェクトなどの間に生じる依存関係をオブジェクトのコードに直接記述せず、外部からなんらかの形で与えるようにする手法。
これを理解するためには、「依存」という意味と「注入」という意味を理解する必要がある。
依存
依存とは一言で言うと「他のクラスを利用しているかどうか」のことである。
例えば下記のコードがあるとする。
public class Car {
private HondaEngine hondaEngine;
public Car(HondaEngine hondaEngine) {
this.hondaEngine = hondaEngine;
}
public void go() {
hondaEngine.startEngine();
hondaEngine.stopEngine();
}
}
public class HondaEngine {
public void startEngine() {
System.out.println("ホンダスタート");
}
public void stopEngine() {
System.out.println("ホンダストップ");
}
}
このコードであれば下記の問題が発生する。
- HondaEngineaが完成するまで、Carクラスを動かすことができない。
- HondaEngineからNissanEngineに変更したいときに修正する箇所が多い。
上記のような依存関係が強いものを「密結合」と呼ぶ。
この依存を弱くするためにインターフェースを使う。
下記が上記のコードを書き換えた例である。
public interface Engine {
public void startEngine();
public void stopEngine();
}
public class HondaEngine implements Engine {
public void startEngine() {
System.out.println("ホンダスタート");
}
public void stopEngine() {
System.out.println("ホンダストップ");
}
}
public class Car {
private Engine engine;
public Car(Engine engine) {
this.engine = engine;
}
public void go() {
engine.startEngine();
engine.stopEngine();
}
}
上記であれば、先ほどと比べて変更に強い構造になっている。
変更に強いとは下記の2点のことである。
- HondaEngineができていなくても、テスト用Engineを作れば、Carクラスは実行できる。
- HondaEngineからNissanEngineに変更したとしても、ここの2つのクラスでは変える必要ない。
このように、各クラスが依存度が低いときを「疎結合」と呼ぶ。
以上が依存についてである。
注入
注入とは
そのクラスの外から定数、変数、インスタンスをあるクラスに代入することである。
先程のコードの続きを考えてみます。
public interface Engine {
public void startEngine();
public void stopEngine();
}
public class HondaEngine implements Engine {
public void startEngine() {
System.out.println("ホンダスタート");
}
public void stopEngine() {
System.out.println("ホンダストップ");
}
}
public class Car {
private Engine engine;
public Car(Engine engine) {
this.engine = engine;
}
public void go() {
engine.startEngine();
engine.stopEngine();
}
}
これに追加して、プログラムを実行するMainクラスを考えます。
public class Main {
public static void main(String[] args) {
Engine hondaEngine1 = new HondaEngine();
Engine hondaEngine2 = new HondaEngine();
Engine hondaEngine3 = new HondaEngine();
Car car1 = new Car(hondaEngine1);
Car car2 = new Car(hondaEngine2);
Car car3 = new Car(hondaEngine3);
car1.go();
car2.go();
car3.go();
}
}
上記のようにインターフェースに変数を代入することを注入という。
少し話は脱線するが、上記は柔軟性に欠けるので、Factoryクラスで解決すると良い。
説明は避けるが下記に変更するとより良い。
public class Factory {
public static Engine createHondaEngine() {
return new HondaEngine();
}
}
public class Main {
public static void main(String[] args) {
Engine hondaEngine1 = Factory.createHondaEngine();
Engine hondaEngine2 = Factory.createHondaEngine();
Engine hondaEngine3 = Factory.createHondaEngine();
Car car1 = new Car(hondaEngine1);
Car car2 = new Car(hondaEngine2);
Car car3 = new Car(hondaEngine3);
car1.go();
car2.go();
car3.go();
}
}
上記にコードを修正することによって、hondaEngineを継承しUpdateしたクラスにもFactoryクラスのnew クラスを変更するだけで対応が可能である。
ここまでが、依存と注入の説明である。
DIに話を戻す。
DIとは上記の2つを同時に、簡単に行ってくれる仕組みである。SpringではDIを自動化してくれるDIコンテナを提供している。
DIによって得られるメリットは下記である。
・コードが簡素になり、開発期間が短くなる
・テストが容易になり、「テスト・ファースト」による開発スタイルを取りやすくなる
・特定のフレームワークへの依存性が極小になるため、変化に強いソフトウエアを作りやすくなる(=フレームワークの進化や、他のフレームワークへの移行に対応しやすくなる)
ここからはSpring上でDIがどう処理されているか、どう実装するかを考察していく。
DIの処理の流れ
全体の流れは以下の2つである。
- コンポーネントスキャン
- インスタンスの作成と注入
コンポーネントスキャン
インスタンスの作成と注入をするための対象クラスを探し出す。
下記はイメージ図
上記のように特定のアノテーションがついたクラスをDIコンテナに追加していく。
特定のアノテーションとは
上記のようなアノテーションがついている、DIコンテナに登録されるJavaクラスをBeanと呼ぶ。
インスタンスの作成と注入
コンポーネントスキャンが終了した後は、DIコンテナが自動で対象のBeanをインスタンス化する。**このときに、呼ばれるときに毎回インスタンス化するのか、シングルトンなのかも含めて設定される。**ここがDIのもう一つの強みである。一行でこの設定ができるのは本当にありがたい。ここまでで依存が解決されたインスタンス(生成するためのロジック)が完了している。
生成されたインスタンスは@Autowiredアノテーションがついたフィールドに注入(代入)される。ちなみに@Autowiredアノテーションがつけられるのは、
- フィールド変数
- コンストラクタの引数
- setterの引数
のみである。
DIの実装はまた今度。
DIについては少しわかったけど、次の難点はコードに落とし込むところだなぁ。。。。