VContainerでFactoryを利用して、複数のComponentの依存を解決したPrefabを作る
はじめに
VContainerはめちゃめちゃ便利なDIコンテナですが、色々と依存性を解決するのが難しい場面もあります。
今回は[Inject]
アトリビュートの付いた複数のComponentに対して依存を解決する書き方の一例を提示します。
状況
例えばPlayerCore
クラスとPlayerMovement
クラスがあるとします。これらは同じPlayer
というGameObjectに紐づいたPrefabとして利用されるという想定です。
これらのComponentには、それぞれ依存先があります。仮にFoo
,Bar
とします。
using UnityEngine;
public class PlayerCore : MonoBehaviour
{
[Inject] private readonly Foo _foo;
}
using UnityEngine;
public class PlayerMovement : MonoBehaviour
{
[Inject] private readonly Bar _bar;
}
失敗例
以下のようにして、Prefabをアタッチしても上手くいきません。
public sealed class GameConfigure : LifetimeScope
{
[SerializeField] private PlayerCore _playerCore;
protected override void Configure(IContainerBuilder builder)
{
builder.Register<Foo>(Lifetime.Singleton);
builder.Register<Bar>(Lifetime.Singleton);
builder.RegisterComponent(_playerCore);
builder.RegisterComponentInNewPrefab(_playerCore, Lifetime.Singleton);
}
}
解決法:Factoryの利用
これらを解決するにはFactoryを登録し、そのメソッドを呼んでやります。
public sealed class GameConfigure : LifetimeScope
{
/* ... */
protected override void Configure(IContainerBuilder builder)
{
builder.Register<Foo>(Lifetime.Singleton);
builder.Register<Bar>(Lifetime.Singleton);
builder.RegisterFactory<PlayerInfo, PlayerCore>(container => (info) =>
{
var player = container.Instantiate(_playerCore); // playerを生成
var playerMovement = player.GetComponent<PlayerMovement>();
container.Inject(playerMovement); // playerMovementの依存を解決
player.Init(info); // playerの初期化処理をしたかったらこんな感じで
return player;
},
Lifetime.Singleton);
builder.RegisterComponent(_playerSpawner);
}
}
public sealed class PlayerSpawner : MonoBehaviour
{
[Inject] private readonly Func<PlayerInfo, PlayerCore> _playerFactory; // Factoryのメソッドがinjectされる
private void Start()
{
var playerInfo = /*...*/
var player = _playerFactory(playerInfo); // playerのPrefabが生成される
}
}
Factoryとは
そもそもFactoryとは何でしょうか?
色々な使い方やパターンがあり、私も正直自信がないのですが…
基本的には、new
の処理をFactoryクラスに逃がすことで抽象的なメソッドに逃がしたり、複雑な生成系をまとめたりするのに使うのかなーーという印象です。
今回は、複雑なクラスの依存関係等を解決したオブジェクトを生成するために使います。
コードの意味
コードの意味について見ていきます。
builder.RegisterFactory<>();
1.FactoryメソッドをRegisterするよ~という意味です。<>
内の引数は一番最後が生成するクラスで、それ以外が生成に使う引数になります。たとえば、
builder.RegisterFactory<int, Vector3, Player>(/*...*/);
と書けば、第一引数にint,第二引数にVector3を持つ、Playerを返すFactoryメソッドが登録されます!
ちなみにこれは以下の構文と一緒です。
builder.Register<Func<int, Vector3, Player>>(/*...*/);
まあ上のような構文が用意されているので、上を使いましょう。
(container => (x) => ...
2.containerはDIのコンテナ、(x)は指定した引数が入ります。
builder.RegisterFactory<int, Vector3, Player>
のように引数を複数指定した場合は、その数だけ引数名がないとコンパイルエラーになります。
つまり、例えば以下のように書く必要があります。
builder.RegisterFactory<int, Vector3, Player>(container => (number, position) => /*...*/ );
3.カッコ内の処理
あとはメソッドの中身の実装です。container.Inject()
を利用すれば依存を解決することが出来るので、これを使いながらFactoryを生成しましょう。
感想
Factoryメソッドをそのまま登録できるのはVContainerの超便利機能だと感じました。Componentの依存を解決したPrefabを作るという用途のほかにも色々な場面でFactoryは重宝するので、活用場面は結構多いのではないでしょうか…!
参考
Discussion