DIPについて勉強し直してみた

こちらは、 CAMエンジニア [Advent Calendar 2019](https://qiita.com/advent-calendar/2019/cam-inc) 9日目の記事です。
昨日は @nonoakij さんの
[Flexboxで「width:0」にしてうまくいく場合について研究してみた](https://qiita.com/nonoakij/items/c586c5a1b6c400688722)でした。



今回は僕が勉強して、業務に生かされていることについて書こうと思います！

## 概要
最近、もっといいコードを書くためにどうしたらいいかと勉強していた中でSOLID原則に出会いました
その中でD（dip）について勉強したので、それについて備忘録的なとこも含め書きたいと思います

## dipとは
The Dependency Inversion Principleのことで訳すと、依存性逆転の原則のことです

> 上位のモジュールは下位のモジュールに依存してはならない。
どちらのモジュールも「抽象」に依存すべきである 「抽象」は実装の詳細に依存してはならない。
実装の詳細が「抽象」に依存すべきである。

では
> 「抽象」に依存すべきである

を具体的に実装するためにはどうしたらいいのか


## diしましょう

### diって？
- Dependency Injectionのこと
- デザインパターンの一つ
- 日本語訳だと「依存性の注入」
- あるオブジェクトを別のオブジェクトに渡すデザインパターンがDIパターン

マイクロソフトのランゲージポータルには、「dependency」について下記のような説明がある
> 「機能Aが機能Bに依存している場合、BがAのdependencyである」

AがBに依存している関係はあまりよろしくない。なので *抽象に依存しよう* って話に繋がります

そして、「依存性の注入」って言われるより
「依存オブジェクトの注入」って言われると、わかりやすいですが実装ベースでの話です


### 特徴
オブジェクトの生成と使用が分離されている
AがBを呼ぶのではなく、Bが外部からAに注入されることにより、制御を反転させることができる


### メリット
具体的なことを書かないので記述量が減る
オブジェクトを再利用できる
保守性、柔軟性が高くなる


### デメリット
具体的なことを書いてないので、コードだけみても何が起きてるか追いづらい


## コード

diに従わないパターンと従うパターンを書いてみました

### 従わないパターン
```typescript

interface ServiceInterface {
  doSomeThing(): void;
}

class Client {
  private service: ServiceInterface;
  constructor () {
    this.service = new Service();
  }

  doSomeThing () {
    this.service.doSomeThing();
  }
}

class Service {
  doSomeThing(): void {}
}
```

### 従ったパターン
```typescript
interface ServiceInterface {
  doSomeThing(): void;
}

class Client {
  private service: ServiceInterface;
  constructor (service: ServiceInterface) {
    this.service = service;
  }

  doSomeThing () {
    this.service.doSomeThing();
  }
}

class Service implements ServiceInterface {
  doSomeThing(): void {}
}
```


### しかし
DIに従えばとりあえず良いってわけではなくて引数が増えちゃうし、準備が大変です

```typescript

interface ServiceInterface {
  doSomeThing(): void;
}

interface ProductInterface {
  useSomething(): void;
}

class Client {
  private service: ServiceInterface;
  private product: ProductInterface;

  constructor (service: ServiceInterface, product: ProductInterface) {
    this.service = service;
    this.product = product;
  }

  doSomeThing () {
    this.service.doSomeThing();
  }

  useSomething () {
    this.product.useSomething();
  }
}

class Service implements ServiceInterface {
  doSomeThing(): void {}
}

class Product implements ProductInterface {
  useSomething (): void {};
}

// うーーーん
new Client(
  new Service(),
  new Product(),
);
```


## DIコンテナを使おう

今は引数が二つだけなので、まだ読めるけどこれがどんどん増えたらつらく大変なのでDIコンテナを使いましょう！
DIコンテナとはDI機能を提供するフレームワークです

- マイクロソフト製のDIコンテナ → [tsyringe](https://github.com/microsoft/tsyringe)
- よくみかけるDIコンテナ → [InversifyJS](https://github.com/inversify/InversifyJS)


僕は両方使ってみましたが、tsyringeの方が簡単に書けるなーって思いましたが
InversifyJSの方が軽量だったので、InversifyJSを使ってみました

InversifyJSで書いたら、大体こんな感じになりました

```typescript:
import {
 injectable,
 inject
} from 'inversify';

import {
  ServiceInterface,
  ProductInterface,
  ClientInterface,
} from '../interface';

import { diTypes } from '../types';

@injectable()
export class Client implements ClientInterface {
  private service: ServiceInterface;
  private product: ProductInterface;

  constructor (
    @inject(diTypes.service) service: ServiceInterface,
    @inject(diTypes.product) product: ProductInterface,
  ) {
    this.service = service;
    this.product = product;
  }

  doSomeThing () {
    this.service.doSomeThing();
  }

  useSomething () {
    this.product.useSomething();
  }
}
```

```typescript:inversify.config.ts
import { Container } from 'inversify';
import { diTypes } from './types';
import {
  ServiceInterface,
  ProductInterface,
  ClientInterface,
} from '../interface';
import {
  Service,
  Product,
  Client
} from './entity/index';

const myContainer = new Container();
myContainer.bind<ServiceInterface>(diTypes.service).to(Service);
myContainer.bind<ProductInterface>(diTypes.product).to(Product);
myContainer.bind<ClientInterface>(diTypes.client).to(Client);

export { myContainer };
```

```typescript:index.ts
import 'reflect-metadata';
import { myContainer } from './inversify.config';
import { diTypes } from './types';
import { ClientInterface } from './interface';

const ohteru = myContainer.get<ClientInterface>(diTypes.client);

export default () => {
  ohteru.doSomeThing();
  ohteru.useSomething();
}
```


## 考察とまとめ

がっつりデコレーターを使って実装してますね
個人的にはブラックボックス化されていてデコレーターは好きじゃないですが
便利なものが多く準備されていることに間違いはないです

なので今回はDIコンテナを使いましたが、自分でDIコンテナを実装してみた方がいいなと思いました


また、僕は実際の業務ではdiは使っていませんが、依存性について意識して製作することによって
柔軟性の高い設計の仕方を学ぶことができました

もっと良いコードについて勉強していこうと思います！

次回は@kooooooさんの記事です！お楽しみに!