VContainerの個人的な使いかた
はじめに
VContainerはUnity向けのシンプルで強力なDIコンテナとして有名ですが、いきなり使っても使い方があまり確立されないように思います(VContainerに限らず、DIコンテナ全般としての話だと思いますが)。
最近、自分なりにある程度納得できる使い方がわかってきたので、参考程度に紹介します。
1 シーン 1 LifetimeScope
VContainerのLifetimeScopeは親子関係を持たせることができます。加えてLifetime.Scopedを利用することでスコープを絞ることができます。これによって依存関係をより厳密に管理することができるのですが、濫用すると逆に複雑になりすぎる場合があります。
シーンごとにLifetimeScopeが管理されていれば十分な場合が多いので、自分は1シーンに1つのLifetimeScopeを持たせるようにしています。
たとえば、
- Title
- InGame
- Setting
のようなシーン構造の場合、それぞれのシーンに対してLifetimeScopeを持たせることで、シーン間の依存関係を明確にすることができます。
public class TitleLifetimeScope : LifetimeScope
{
protected override void Configure(IContainerBuilder builder)
{
// ...
}
}
public class InGameLifetimeScope : LifetimeScope
{
protected override void Configure(IContainerBuilder builder)
{
// ...
}
}
public class SettingLifetimeScope : LifetimeScope
{
protected override void Configure(IContainerBuilder builder)
{
// ...
}
}
シーンより大きなライフタイムにはRootLifetimeScope
シーンをまたぐライフタイムをもつようなクラスにはRootLifetimeScopeを使います。
このLifetimeScopeはゲームの起動中常に存在し、どのシーン上でも依存することができます。
- データベース
- レポジトリ
- シーン遷移用クラス
などに使っています。
全部Lifetime.Singletonで登録
VContainerのLifetime.Singletonを使うことで、同じインスタンスを使い回すことができます。
Lifetime.Scopedを使えば先述のような構成でスコープを絞れますが、そもそも1シーン1LifetimeScopeで管理しているのでLifetime.Singletonと意味は変わりません。
毎回新しいインスタンスが返ってくるLifetime.Transientに関してはクセが強く、大抵の場合使わなくても要件が実現できるので採用していません。
MonoBehaviourにInjectしない
MonoBehaviourに対しても、メソッドインジェクション等を利用して依存注入を行うことができますが、自分はあまり使わないようにしています。
VContainerのメリットの1つに、処理の起点をPureC#にできるという点があります。これはRegisterEntryPointなどの機能で簡単に実現できます。
これによって
-
PureC#という安定かつ制御可能なライフタイムの中で処理を書き -
MonoBehaviourという不安定なライフタイムを持っているクラスでイベントの発火や表示、Unityの機能の利用に専念する
という使い分けができます。
こうすると、機能等の安定した実装がそのまま安定したライフタイムによって管理され、実際のゲームオブジェクト等の不安定なインスタンスはそのまま不安定なライフタイムによって表現できます。
この意識でVContainerを利用すると、全体的な依存関係はPureC# -> MonoBehaviourの方向になるため、MonoBehaviourにInjectしない方がよいことになります。
これについてはVContainerにもそれっぽい記述があります。
MonoBehaviour へロジックへの参照をインジェクトするよりは、MonoBehaviour を参照をインジェクト される側 とすることをどちらかというと推奨しています。
EntryPointに依存しない
RegisterEntryPoint()によって登録されたクラスは強引にAsSelf()を利用することで他クラスの依存の対象にもなれますが、この機能は使っていません。
というのも、EntryPointは自分から動くクラスのことなので、そのクラスが他人から動かされると途端に挙動が分かりにくくなるからです。
EntryPointは誰にも依存されず、ただイベント関数のみによって自分から動くイメージで使っています。
これによって起こる問題の対処法
MonoBehaviourからPureC#に依存したい・EntryPointに依存したい
Observerパターンやイベントを使うことでたいてい解決できます。
View -> Modelの依存関係は守りたいけどPureC#起点だと逆になっちゃう
インターフェースを使って依存性の逆転をすることで制御フローを整えることができます。
(自分はそもそもViewとModel等のレイヤーを厳密にアセンブリ切ったりして作ってはいないのでこの辺りはあまりやっていません…!)
おわりに
自分なりの使い方ですが、参考になれば幸いです。
Discussion