🐥

UnityのDI超ざっくり入門 4 - DIコンテナを利用したPureC#主体の開発のメリット・デメリット

2024/06/09に公開

はじめに

前回の記事では、UnityのDIコンテナとして有名なVContainerの基本的な使い方を紹介しました。

https://zenn.dev/qemel/articles/14d247b9945527

その際、PureC#でエントリポイントを含めた設計がしやすいことや、実行順を気にしない簡単な記述で書けるなどのメリットがあることを確認しました。

ですが、そんなDIコンテナ(正確には、DIコンテナを利用したPureC#メインの開発)にも弱点があると思っているので、今回は、DIコンテナ、ひいてはPureC#をメインに使うメリット・デメリットについて見ていきます。

DIコンテナを使ってPureC#を含めるメリット

まずは前回の確認的な部分にもなりますが、メリットについて見ていきます。

PureC#でエントリポイントを含めた設計がしやすい

前回の記事でも触れましたが、DIコンテナを使うことでPureC#でエントリポイント(Start()とかUpdate()とかを使えるクラス)を含めた設計がしやすくなります。

public class GameLifetimeScope : LifetimeScope
{
    protected override void Configure(IContainerBuilder builder)
    {
        // エントリポイントの登録
        builder.RegisterEntryPoint<GameLoopSystem>(Lifetime.Singleton);
    }
}

VContainerを使う場合、ものの一行でStart()Tick()といったUnityのイベント関数の代替的な関数を使うことが出来ます。

Unityでは当たり前のようにすべてのMonoBehaviourクラスが使えるイベント関数ですが、こういったものを備えたクラス(いわゆるエントリポイント)が乱立するのは、設計上あまりよろしくないことがあります。

エントリポイントはそもそも特別なクラスで、そのクラスが主体的に自分から動くことを意味します。こういったクラスを無暗にたくさん作ると、全員が主体的に動くことになり、設計が複雑になりがちです。

ロジックをPureC#で書き、必要な時だけエントリポイントとして登録してやることで、この問題に対する意識が上がりやすいと考えます。

実行順を気にしない簡単な記述で書ける

DIコンテナを使うことで、実行順を気にせずにとりあえず登録しまくれます。

コンストラクタを使ったDIの場合は、依存関係が解決されるまでの順番に気を使わないといけませんが、DIコンテナを使うことで、その辺りを気にせずに書けるのは大きなメリットです。

PureC#のメリットをそのまま享受できる

PureC#のメリットをそのまま享受できるのも大きなメリットです(第2回参照)。

  • 依存関係を明示的にしやすい
  • わかりづらいMonoBehaviourのライフサイクルを気にしなくていい
  • コンストラクタが使える
  • 不要な内部的なコードがなくなる
  • テストがしやすい
  • メモリ消費量が少ない
  • ヒエラルキーに置かなくていいので、ヒエラルキーがスッキリする

DIコンテナを使ってPureC#を含めるデメリット

以上のようにDIコンテナを使うメリットは多いですが、デメリットもあると考えます。

動的な生成が難しい

DIコンテナは、起動時に生成されるべきクラスをSingletonで登録しまくる使い方の範疇であればかなり楽に使うことができるのですが、動的な生成をしたい場合は少し難しいです。

動的な生成をする場合は、色々なパターンがあります。

  • 生成して参照さえ解決(Inject)すればいいMonoBehaviourクラス
  • 生成時にパラメータを渡して初期化したいMonoBehaviourクラス
  • 生成した後他クラスから参照されるMonoBehaviourクラス
  • 生成した後他クラスに紐づけされ、その後も動的に取得する必要のあるMonoBehaviourクラス
  • それに紐づけされるPureC#クラス、さらにそのPureC#が参照する別のPureC#クラス(MVPパターンとかを動的に生成するとこうなりがち)

など、色々なパターンがあるため、それぞれのパターンに対しての適切な対応が必要です。

この際はFactoryパターンを使って生成系をまとめ、そのまとめたFactoryクラス自体を登録してみたり、パラメータをScriptableObjectにしておいて、それをRegisterComponent()したりと色々考える必要があります...。

この辺りは慣れるまで大変...というか私もまだ慣れてません!

動的な生成に対して対応が難しいのは第2回でのコンストラクタを使ったUnityDIのときとあまり変わらないままなので、ここは少し辛いところです。

UnityのMonoBehaviourはこの辺りが優秀で、他クラスの取得が非常に容易なので、DIコンテナを使う際もUnity本来のいい機能との丁度いい折り合いをつける必要があると感じます。

勿論、他クラスの取得が簡単なのはデメリットでもあることは第1回にて説明した通りです。

コンポーネント的な設計が難しい

UnityのMonoBehaviourは、そういう思想で作られているだけあってコンポーネント的な設計が得意です。

MonoBehaviourクラスを作ってInspector上でペタペタ貼るだけで、それらの機能を合体させたオブジェクトを作り上げることが可能です。

一方、DIコンテナを使ってPureC#寄りの開発をした際、コンポーネント的な設計は1テンポ多くなる印象です。

このあたりもUnityとPureC#のいいとこ取りをして開発することが求められると感じます。

使いだしたらそのプロジェクトではずっと使い続けることになる

DIコンテナを使い始めると、そのプロジェクトではずっと使い続けることになります。

他クラスがPureC#で管理される以上、Unityの機能でクラスを取得することができないので、結局DIで登録されたクラスをどこかしらで解決する作業が必要になるからです。

最初は小さなプロジェクトからDIコンテナを試してみることを強くお勧めします。

まとめ

DIコンテナ、ひいてはPureC#を手放しに好意的に紹介してもあまりよくないと感じたので、こういった話を書きました。

VContainer等のDIコンテナ、ひいてはそういったDIを用いたUnityでの開発はとても効果的に働くことも多いですが、やや過剰になってしまう可能性もあります。

適切なバランスを取りながら、UnityのいいところとPureC#のいいところを取り入れた開発を進めることが大事だと感じます。

また、今回の話はPureC#がメインになってしまいましたが、MonoBehaviourだけの開発でVContainerを使うことも当然可能なので、好みのバランスを見つけることがいいのではないかと思います。

VContainer等を使ってみたい方は、まずは動的な生成が少ない小さなプロジェクトから試してみることをお勧めします。

Discussion