前回の続きです。
今回はprovider
を試します。
Value Provider
を試す
この機能を使うと文字列や数値のような値、既存のオブジェクトもコンテナで管理することができます。
早速動かしてみます。
「既存オブジェクト」の確認をするためのクラス。
export default class SystemSetting {
public settingCode: string | null = null;
public settingName: string | null = null;
}
利用する側のクラス
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}]`);
}
}
エントリポイント
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
の情報はコンパイルのときに消える(間違ってたらすみません。)ので一工夫必要です。
インターフェース
export default interface FooLogic {
doFoo(): string
}
実装クラス
import { injectable } from "tsyringe";
import FooLogic from "./FooLogic";
//↓実装クラスはコンテナから取得できる状態にしておかないとダメです
@injectable()
export default class FooLogicImpl implements FooLogic {
constructor() {
console.log("new FooLogicImpl()")
}
doFoo() {
return "foo";
}
}
インターフェースを利用するクラス
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());
}
}
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
を試す
インスタンスのファクトリ関数も登録できるようです。
試してみます。
こっちは今まで通りのやり方でコンテナで管理されるもの
import { injectable } from "tsyringe";
@injectable()
export default class BarLogic {
constructor() {
console.log("new BarLogic()");
}
doBar() {
return "bar";
}
}
こっちはファクトリ関数で生成されるもの
import BarLogic from "./BarLogic";
//injectableなどはつけない
export default class HogeService {
constructor(
private barLogic: BarLogic
) {
console.log("new HogeService()");
}
doHoge() {
console.log(this.barLogic.doBar());
}
}
エントリポイント
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
を使うと、ファクトリ関数の結果がキャッシュされます。
エントリポイントのみ変更
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回続けて書きましたが今回で終わりです。
他にも便利な機能があるようですが、ひとまずこれだけのものを使えば最低限のことはできそうな気がします。