LoginSignup
5

More than 3 years have passed since last update.

TSyringeを動かしてみた(3)

Last updated at Posted at 2020-05-04

前回の続きです。

今回はprovider を試します。

Value Provider を試す

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

早速動かしてみます。

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

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

利用する側のクラス

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}]`);
    }
}

エントリポイント

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メソッドを使用し、第1引数にトークン(キーとなる情報)、第2引数にValueProvider 型の情報を渡します。

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

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

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

Class Provider を試す

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

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

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

インターフェース

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

実装クラス

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

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

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

    doFoo() {
        return "foo";
    }
}

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

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());
    }
}
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メソッドを使用し、第1引数にトークン(キーとなる情報)、第2引数にClassProvider 型の情報を渡します。

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

Factory Provider を試す

インスタンスのファクトリ関数も登録できるようです。

試してみます。

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

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

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

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

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());
    }
}

エントリポイント

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 を使うと、ファクトリ関数の結果がキャッシュされます。

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

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

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

instance-caching-factory.tsを見るとundefinedでなければキャッシュしていることがわかります。

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

他に

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

最後に

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

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
5