何故DIコンテナが必要なのか

「何故DIコンテナが必要なのか」について、自分なりに説明を試みる。

## 目次
1. DIコンテナ以前に、そもそも「DI」がなぜ必要なのか
2. 問題点：「DIでは一体誰が`new`するべきか？」
3. `new`するためのクラスを別に作る
4. `new`をDIコンテナに任せる
5. Q&A

## 1. DIコンテナ以前に、そもそも「DI」がなぜ必要なのか
そもそも「DI(依存性注入)」がなぜ必要なのかを説明する。

下記のように、ハードウェア制御クラス`GreatHardware`があり、クラス`LowLayer`はそれを利用しているとする。クラス`GreatHardware`は、実際のハードウェアがないと動かないとする。

この構成には問題がある。クラス`LowLayer`が、実際のハードウェアがないと動かないクラス`GreatHardware`を直接`new`してしまっているため、実際のハードウェアがないとテストできない。

```ts
// 構成例1
class GreatHardware {
    somethingGreat(){
        // 何かハードウェア依存の処理を行う
    }
}

class LowLayer {
    hardware:GreatHardware;
    constructor(){
        this.hardware = new GreatHardware();
    }

    doHoge() {
        this.hardware.somethingGreat();
    }
}
```

これを解決するには、下記の構成例2のように「DI(依存性注入)」を使うことができる。ここでは、`LowLayer`のコンストラクタからGreatHardwareのインスタンスを注入している。これによりインターフェース`IGreatHardware`を実装したモックを`LowLayer`のコンストラクタに注入すれば実際のハードウェアがなくても`LowLayer`のテストができるということになる。


```ts
// 構成例2
interface IGreatHardware {
    somethingGreat();
}

class GreatHardwareImpl implements IGreatHardware {
    somethingGreat(){
        // 何かハードウェア依存の処理を行う
    }
}

class LowLayer {
    hardware:IGreatHardware;
    constructor(hardware:IGreatHardware){
        this.hardware = hardware;
    }

    doHoge() {
        this.hardware.somethingGreat();
    }
}
```

## 2. 問題点：「DIでは一体誰が`new`するべきか？」

しかし、めでたしめでたしとはいかない。構成例2を実際に動かすには、誰かが`GreatHardware`を`new`して`LowLayer`に渡さなくてはならない。

例えば構成例3が考えられるだろう。しかし、これでは構成例1と同じ問題が起こる。結局`MiddleLayer`が`GreatHardware`を`new`しなければならず、`MiddleLayer`が`GreatHardware`に依存してしまうからだ。

```ts
// 構成例3
interface IGreatHardware {
    somethingGreat();
}

class GreatHardwareImpl implements IGreatHardware {
    somethingGreat(){
        // 何かハードウェア依存の処理を行う
    }
}

class LowLayer {
    hardware:IGreatHardware;
    constructor(hardware:IGreatHardware){
        this.hardware = hardware;
    }

    doHoge() {
        this.hardware.somethingGreat();
    }
}

class MiddleLayer {
    lowLayer:LowLayer;
    constructor(){
        this.lowLayer = new LowLayer(new GreatHardware());// <- 結局newしている
    }

    doFuga(){
        this.lowLayer.doHoge();
    }
}
```

ではさらに上のレイヤーに`GreatHardware`を`new`する責任を持たせるようにしたらどうだろうか(構成例4)。

しかし、結局これだとUIレイヤーが具体的なハードウェアがないと動かないクラスに依存することになってしまった。

構成例1よりもまずい状況なのではないだろうか？

```ts
// 構成例4
interface IGreatHardware {
    somethingGreat();
}

class GreatHardwareImpl implements IGreatHardware {
    somethingGreat(){
        // 何かハードウェア依存の処理を行う
    }
}

interface ILowLayer {
    doHoge();
}

class LowLayerImpl implements ILowLayer {
    hardware:IGreatHardware;
    constructor(hardware:IGreatHardware){
        this.hardware = hardware;
    }

    doHoge() {
        this.hardware.somethingGreat();
    }
}

interface IMiddleLayer {
    doFuga();
}

class MiddleLayerImpl implements IMiddleLayer {
    lowLayer:ILowLayer;
    constructor(lowLayer:ILowLayer){
        this.lowLayer = lowLayer;
    }

    doFuga(){
        this.lowLayer.doHoge();
    }
}

interface IHighLayer {
    doFoo();
}

class HighLayerImpl implements IHighLayer {
    middleLayer:IMiddleLayer;
    constructor(middleLayer:IMiddleLayer){
        this.middleLayer = middleLayer;
    }

    doFoo(){
        this.middleLayer.doHoge();
    }
}

class UIController {
    highLayer:IHighLayer;
    constructor(){
        this.highLayer = new HighLayerImpl(new MiddleLayerImpl(new LowLayerImpl(new GreatHardwareImpl())));// <- UIが実際のハードウェアがないと動かないクラスに依存している。
        this.highLayer.doFoo();
    }
}

```

## 3. `new`するためのクラスを別に作る
構成例4を見たとき、

```ts
new HighLayerImpl(new MiddleLayerImpl(new LowLayerImpl(new GreatHardwareImpl())));
```

この部分をさらに誰か別のクラスの責任にしたらいいのでは？と思う人がいると思う。

それをやって見たのが構成例5である(UIControllerより下のレイヤはこれまでの構成例と同じなので省略)。

```ts
// 構成例5
class UIController {
    highLayer:IHighLayer;
    constructor(){
        this.highLayer = GreatFactory.createHithLayer();
        this.highLayer.doFoo();
    }
}

class GreatFactory {
    static createHighLayer(){
        return new HighLayerImpl(new MiddleLayerImpl(new LowLayerImpl(new GreatHardwareImpl())));
    }
}
```

さらに一貫性の観点から、`UIController`インスタンスの生成も`GreatFactory`が任されると考えてみよう。構成例6である。

```ts
// 構成例6
class UIController {
    highLayer:IHighLayer;
    constructor(highLayer:IHighLayer){
        this.highLayer = highLayer;
        this.highLayer.doFoo();
    }
}

class GreatFactory {
    static createUiController(){
        return new UIController(new HighLayerImpl(new MiddleLayerImpl(new LowLayerImpl(new GreatHardwareImpl()))));
    }
}
```

これによって全てのレイヤーが分離し、相互依存がない状態になった。GreatFactory以外のクラスの依存性は外部から注入でき、モックテストができる。

ただ、最後の問題は`GreatFactory`だ。ここのメンテナンス性には問題がある。例えば、`MiddleLayerImpl`のコンストラクタ引数が増加したらどうだろうか。メンテナンスが大変ではないだろうか。

## 4. `new`をDIコンテナに任せる
これをDIコンテナを使って書き直すとすごく簡単になる。

ここでは簡単のため、架空のDIコンテナを用いる。

この架空のDIコンテナは、コンストラクタの前に`@Inject()`を記載すると、別途書いた設定通りに引数の型に対応するインスタンスを生成してセットしてくれる。TypeScriptの`interface`は実行時には認識できないが、何かの魔法で認識されるものとする。`@Inject`はDIコンテナが動いていない場合は（例えばテストの時など）、何も書いていないのと同じになる。

このDIコンテナの設定ファイルは下記のように書く。`=`の左辺の型には、右辺のクラスのインスタンスが渡される。

```text
IHighLayer=HighLayerImpl
IMiddleLayer=MiddleLayerImpl
ILowLayer=LowLayerImpl
IGreatHardware=GreatHardwareImpl
```

構成例7にこのDIコンテナを使った例を示す。

```ts
// 構成例7
interface IGreatHardware {
    somethingGreat();
}

class GreatHardwareImpl implements IGreatHardware {
    somethingGreat(){
        // 何かハードウェア依存の処理を行う
    }
}

interface ILowLayer {
    doHoge();
}

class LowLayerImpl implements ILowLayer {
    hardware:IGreatHardware;
    
    @Inject()
    constructor(hardware:IGreatHardware){
        this.hardware = hardware;
    }

    doHoge() {
        this.hardware.somethingGreat();
    }
}

interface IMiddleLayer {
    doFuga();
}

class MiddleLayerImpl implements IMiddleLayer {
    lowLayer:ILowLayer;

    @Inject()
    constructor(lowLayer:ILowLayer){
        this.lowLayer = lowLayer;
    }

    doFuga(){
        this.lowLayer.doHoge();
    }
}

interface IHighLayer {
    doFoo();
}

class HighLayerImpl implements IHighLayer {
    middleLayer:IMiddleLayer;

    @Inject()
    constructor(middleLayer:IMiddleLayer){
        this.middleLayer = middleLayer;
    }

    doFoo(){
        this.middleLayer.doHoge();
    }
}

class UIController {
    highLayer:IHighLayer;

    @Inject()
    constructor(highLayer:IHighLayer){
        this.highLayer = highLayer;
        this.highLayer.doFoo();
    }
}
```

これにより設定通りにインスタンスを生成してくれる。

メンテナンス性も向上している。下記のようにMiddleLayerImplのコンストラクタの引数が増えたときも、

```ts

class MiddleLayerImpl implements IMiddleLayer {
    lowLayer:ILowLayer;

    @Inject()
    constructor(lowLayer:ILowLayer, otherLayer:IOtherLayer){ // <- otherLayerが増えた
        this.lowLayer = lowLayer;
    }

    doFuga(){
        this.lowLayer.doHoge();
    }
}
```

下記のようにDIコンテナの設定に追記すれば正しくインスタンスを生成してセットしてくれる。

```text
IHighLayer=HighLayerImpl
IMiddleLayer=MiddleLayerImpl
ILowLayer=LowLayerImpl
IGreatHardware=GreatHardwareImpl
IOtherLayer=OtherLayerImpl // <- otherLayerの設定を追加
```

## 5. Q&A
- Q1. 大げさすぎるように思える。なぜ本番のシステムにDIコンテナという複雑な仕組みを持ち込む必要があるのだろうか。むしろ、テストの時だけ使う仕組みを用意して、`GreatHardware`を差し替えればそれで済むのではないだろうか。
- A. 「テストの時だけに使う仕組み」はむしろ危険なことが多い。テストの時だけにしか使わないから、もしバグがあったときに非常に見つかりにくい。テストの時はできるだけ仕組みを使わないべきだと思う。その点、DIコンテナを使った構成であれば、テストのときはクラスはただのクラスであり、単純にテストすれば良いので非常にシンプルになる。