TSyringeを動かしてみた(3)

[前回](https://qiita.com/epsilon_87_myo/items/a3a8eb3bf492230d419f)の続きです。

今回は`provider` を試します。

## `Value Provider` を試す
この機能を使うと文字列や数値のような値、既存のオブジェクトもコンテナで管理することができます。

早速動かしてみます。

「既存オブジェクト」の確認をするためのクラス。

```ts:SystemSetting.ts
export default class SystemSetting {
    public settingCode: string | null = null;
    public settingName: string | null = null;
}
```

利用する側のクラス

```ts:SandboxService.ts
import { injectable, inject } from "tsyringe";
import SystemSetting from "./SystemSetting";

@injectable()
export default class SandboxService {

    constructor(
        @inject("systemCode") private systemCode: string,
        @inject("setting") private setting: SystemSetting
    ) {
    }

    showSystemCode(){
        console.log(`systemCode=${this.systemCode}`);
    }
    
    showSetting(){
        console.log(`Setting[settingCode=${this.setting.settingCode}, settingName=${this.setting.settingName}]`);
    }
}
```

エントリポイント

```ts:index.ts

import "reflect-metadata";
import { container } from "tsyringe";
import SystemSetting from "./SystemSetting";
import SandboxService from "./SandboxService";

//値の登録
container.register("systemCode", { useValue: "001" });

//オブジェクトの登録
const setting = new SystemSetting();
setting.settingCode = "010";
setting.settingName = "it";
container.register("setting", { useValue: setting });

const sandboxService = container.resolve(SandboxService);
sandboxService.showSystemCode();
sandboxService.showSetting();
```

実行結果です。

```
systemCode=001
Setting[settingCode=010, settingName=it]
```

### 解説
値を登録するときはコンテナの`register`メソッドを使用し、第１引数にトークン(キーとなる情報)、第２引数に`ValueProvider` 型の情報を渡します。

トークンに利用することができるのは文字列、シンボル、コンストラクタ、`DelayedConstructor`と呼ばれるものが使用可能です。

利用するクラス(`SandboxService`)では `@inject` でトークンを指定します。これをすることで値がインジェクションされます。

`@injectAll` を使うと複数のインスタンスを配列で受け取ることができます。

## `Class Provider` を試す
実際に使うことが多いであろうインターフェースに対応する型をインジェクションしてみます。

他の言語のDIコンテナでは 利用側のクラスはインターフェースをインジェクションし、テストのときにはモックに差し替えるということをするかと思います。

これと似たような事を、`TSyringe`でも可能ですが`TypeScript` だと`interface` の情報はコンパイルのときに消える(間違ってたらすみません。)ので一工夫必要です。

インターフェース

```ts:FooLogic.ts
export default interface FooLogic {
    doFoo(): string
}
```

実装クラス

```ts:FooLogicImpl 
import { injectable } from "tsyringe";
import FooLogic from "./FooLogic";

//↓実装クラスはコンテナから取得できる状態にしておかないとダメです
@injectable()
export default class FooLogicImpl implements FooLogic {

    constructor() {
        console.log("new FooLogicImpl()")
    }

    doFoo() {
        return "foo";
    }
}
```

インターフェースを利用するクラス

```ts:HogeService.ts
import { injectable, inject } from "tsyringe";
import FooLogic from "./FooLogic";

@injectable()
export default class HogeService {

    constructor(
        //↓トークンを使って指定する。
        @inject("FooLogic") private fooLogic: FooLogic,
    ) {
        console.log("new HogeService()");
    }

    doService() {
        console.log(this.fooLogic.doFoo());
    }
}
```

```ts:index.ts
import "reflect-metadata";
import { container } from "tsyringe";
import FooLogicImpl from "./FooLogicImpl";

import HogeService from "./HogeService";

//↓トークンと実際に使用する型を登録
container.register("FooLogic", { useClass: FooLogicImpl })

const hogeService = container.resolve(HogeService);
hogeService.doService();

```

実行結果

```
new FooLogicImpl()
new HogeService()
foo
```


### 解説
インターフェースに対応する型を登録するときはコンテナの`register`メソッドを使用し、第１引数にトークン(キーとなる情報)、第２引数に`ClassProvider` 型の情報を渡します。

こちらでは、インターフェースの代わりにトークンを用いてコンテナに情報を登録します。


## `Factory Provider` を試す
インスタンスのファクトリ関数も登録できるようです。

試してみます。

こっちは今まで通りのやり方でコンテナで管理されるもの

```ts:BarLogic.ts
import { injectable } from "tsyringe";

@injectable()
export default class BarLogic {
    constructor() {
        console.log("new BarLogic()");
    }
    doBar() {
        return "bar";
    }
}
```

こっちはファクトリ関数で生成されるもの

```ts:HogeService.ts
import BarLogic from "./BarLogic";

//injectableなどはつけない
export default class HogeService {

    constructor(
        private barLogic: BarLogic
    ) {
        console.log("new HogeService()");
    }

    doHoge() {
        console.log(this.barLogic.doBar());
    }
}
```

エントリポイント

```ts:index.ts
import "reflect-metadata";
import { container, DependencyContainer } from "tsyringe";
import BarLogic from "./BarLogic";
import HogeService from "./HogeService";

container.register("HogeService", {
    useFactory: (dependencyContainer: DependencyContainer) => {
        console.log("factory!")
        const barLogic = dependencyContainer.resolve(BarLogic)
        return new HogeService(barLogic);
    }
})

console.log("------")
const hogeService1 = container.resolve<HogeService>("HogeService")
hogeService1.doHoge();

console.log("------")
const hogeService2 = container.resolve<HogeService>("HogeService")
hogeService2.doHoge();
```

実行結果

```
------
factory!
new BarLogic()
new HogeService()
bar
------
factory!
new BarLogic()
new HogeService()
bar
```

`HogeService` の解決にファクトリ関数が使われている、それだけ..

### `instanceCachingFactory` を試す。

先ほどの例では`HogeService` を要求するたびにファクトリ関数が実行されていましたが`instanceCachingFactory` を使うと、ファクトリ関数の結果がキャッシュされます。

エントリポイントのみ変更

```ts:index.ts
import "reflect-metadata";
import { container, instanceCachingFactory, DependencyContainer } from "tsyringe";
import BarLogic from "./BarLogic";
import HogeService from "./HogeService";

container.register("HogeService", {
    //↓instanceCachingFactoryでラップした
    useFactory: instanceCachingFactory<HogeService>((dependencyContainer: DependencyContainer) => {
        console.log("factory!")
        const barLogic = dependencyContainer.resolve(BarLogic)
        return new HogeService(barLogic);
    })
})

console.log("------")
const hogeService1 = container.resolve<HogeService>("HogeService")
hogeService1.doHoge();

console.log("------")
const hogeService2 = container.resolve<HogeService>("HogeService")
hogeService2.doHoge();
```

実行結果

```
------
factory!
new BarLogic()
new HogeService()
bar
------
bar
```

ファクトリ関数が１回しか実行されていないことがわかります。

[instance-caching-factory.ts](https://github.com/microsoft/tsyringe/blob/v4.1.0/src/factories/instance-caching-factory.ts)を見ると`undefined`でなければキャッシュしていることがわかります。

ファクトリには他にも条件分岐ができる`predicateAwareClassFactory`もあるようです。


## 他に

他には `Token Provider` というトークンの別名を定義できるものがあるようです。


## 最後に
`TSyringe`について３回続けて書きましたが今回で終わりです。
他にも便利な機能があるようですが、ひとまずこれだけのものを使えば最低限のことはできそうな気がします。