6
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

DIは美しいデザインパターン

Posted at

こんにちは。お久しぶりです。

Springで開発中なのですが、なんとも難しい。。。
今日はDIについて書こうかなと思います。

DI(依存性注入)の定義

コンピュータプログラムのデザインパターンの一つで、オブジェクトなどの間に生じる依存関係をオブジェクトのコードに直接記述せず、外部からなんらかの形で与えるようにする手法。

これを理解するためには、「依存」という意味と「注入」という意味を理解する必要がある。

依存

依存とは一言で言うと「他のクラスを利用しているかどうか」のことである。

例えば下記のコードがあるとする。

Car.java
public class Car {

    private HondaEngine hondaEngine;

    public Car(HondaEngine hondaEngine) {
        this.hondaEngine = hondaEngine;
    }

    public void go() {
        hondaEngine.startEngine();
        hondaEngine.stopEngine();
    }
}
HondaEngine.java
public class HondaEngine {

    public void startEngine() {
        System.out.println("ホンダスタート");
    }

    public void stopEngine() {
        System.out.println("ホンダストップ");
    }
}

上記のクラスをER図で書くと下記のようになる。
スクリーンショット 2021-10-26 19.06.13.png

このコードであれば下記の問題が発生する。

  1. HondaEngineaが完成するまで、Carクラスを動かすことができない。
  2. HondaEngineからNissanEngineに変更したいときに修正する箇所が多い。

上記のような依存関係が強いものを「密結合」と呼ぶ。

この依存を弱くするためにインターフェースを使う。

下記が上記のコードを書き換えた例である。

Engine.java
public interface Engine {
    public void startEngine();

    public void stopEngine();
}
HondaEngine.java
public class HondaEngine implements Engine {

    public void startEngine() {
        System.out.println("ホンダスタート");
    }

    public void stopEngine() {
        System.out.println("ホンダストップ");
    }
}
Car.java
public class Car {

    private Engine engine;

    public Car(Engine engine) {
        this.engine = engine;
    }

    public void go() {
        engine.startEngine();
        engine.stopEngine();
    }
}

上記であれば、先ほどと比べて変更に強い構造になっている。

変更に強いとは下記の2点のことである。

  1. HondaEngineができていなくても、テスト用Engineを作れば、Carクラスは実行できる。
  2. HondaEngineからNissanEngineに変更したとしても、ここの2つのクラスでは変える必要ない。

このように、各クラスが依存度が低いときを「疎結合」と呼ぶ。

以上が依存についてである。

注入

注入とは
そのクラスの外から定数、変数、インスタンスをあるクラスに代入することである。

先程のコードの続きを考えてみます。

Engine.java
public interface Engine {
    public void startEngine();

    public void stopEngine();
}
HondaEngine.java
public class HondaEngine implements Engine {

    public void startEngine() {
        System.out.println("ホンダスタート");
    }

    public void stopEngine() {
        System.out.println("ホンダストップ");
    }
}
Car.java
public class Car {

    private Engine engine;

    public Car(Engine engine) {
        this.engine = engine;
    }

    public void go() {
        engine.startEngine();
        engine.stopEngine();
    }
}

これに追加して、プログラムを実行するMainクラスを考えます。

Main.java
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クラスで解決すると良い。
説明は避けるが下記に変更するとより良い。

Factory.java
public class Factory {
    
    public static Engine createHondaEngine() {
        return new HondaEngine();
    }
}
Main.java
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つである。

  1. コンポーネントスキャン
  2. インスタンスの作成と注入

コンポーネントスキャン

インスタンスの作成と注入をするための対象クラスを探し出す。
下記はイメージ図
スクリーンショット 2021-10-26 19.52.46.png

上記のように特定のアノテーションがついたクラスをDIコンテナに追加していく。
特定のアノテーションとは

上記のようなアノテーションがついている、DIコンテナに登録されるJavaクラスをBeanと呼ぶ。

インスタンスの作成と注入

コンポーネントスキャンが終了した後は、DIコンテナが自動で対象のBeanをインスタンス化する。**このときに、呼ばれるときに毎回インスタンス化するのか、シングルトンなのかも含めて設定される。**ここがDIのもう一つの強みである。一行でこの設定ができるのは本当にありがたい。ここまでで依存が解決されたインスタンス(生成するためのロジック)が完了している。

生成されたインスタンスは@Autowiredアノテーションがついたフィールドに注入(代入)される。ちなみに@Autowiredアノテーションがつけられるのは、

  • フィールド変数
  • コンストラクタの引数
  • setterの引数
    のみである。

DIの実装はまた今度。

DIについては少しわかったけど、次の難点はコードに落とし込むところだなぁ。。。。

6
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?