相互参照するクラス
コンストラクタで依存関係を注入する設計にしている場合、相互参照をどのように処理するかは頭が痛い問題です。依存関係を整理できれば一番良いのですが、そうはいかないケースもありますよね。
かといって、外部クラスを直接参照するようなコードを書いて、ユニットテストではSpy等で無理やりモックしてしまうとコードはスパゲッティになっていきます。(今日仕事でそんなコードを見てしまい、勢いでこの記事を書いています。)
LazyIntializerを使った相互参照
LazyIntializerはその名の通り初期化処理を遅延実行するので、相互参照があっても問題なくインスタンスを生成できます。初期化処理はlockによって保護されるので、非同期呼び出しが複数あっても1回だけ実行されます。
インスタンス生成後のアクセスは非同期処理を通らないので高速に実行されます。
TypeScriptでDouble Checked Lockを使う
インタフェイスを注入しておけばテスト時には簡単にモックできますし、簡易的なDI Frameworkとしても利用できます。
import {LazyInitializer} from "ya-syn";
class ServiceA {
constructor(readonly serviceB: LazyInitializer<ServiceB>) {
}
async methodA() {
console.log("methodA called")
const serviceB = await this.serviceB.get()
await serviceB.methodB()
}
}
class ServiceB {
constructor(readonly serviceA: LazyInitializer<ServiceA>) {
}
async methodB() {
console.log("methodB called")
}
}
class Provider {
readonly serviceA: LazyInitializer<ServiceA> = new LazyInitializer(async () => {
return new ServiceA(this.serviceB)
})
readonly serviceB: LazyInitializer<ServiceB> = new LazyInitializer(async () => {
return new ServiceB(this.serviceA)
})
}
async function main() {
const p = new Provider()
const serviceA = await p.serviceA.get()
await serviceA.methodA()
}
main()
実行結果
% npx ts-node src/di.ts
methodA called
methodB called
ya-syn
セマフォがあるとTypeScriptでコードを書くのがグッと楽になります。ぜひ使ってみてください。