💉

【Unity開発】とにかく試してみるExtenject入門

2024/04/06に公開

Zenjectを使うと、様々な依存性注入を行えます。
あるオブジェクトが他に必要なオブジェクトを利用したり、インターフェースだけ知ってるけどインスタンスはどこで作ろう?といった悩みから解放されるだけでなく、クラスの設計を綺麗にするうえで重宝します。

Extenjectの導入

UnityAssetStoreからダウンロードできます。

UnityAssetStore: https://assetstore.unity.com/packages/tools/utilities/extenject-dependency-injection-ioc-157735

Extenjectの基本

例として、IReceiver をプロパティに持っている SampleObjectController に、IReceiver を実装した具象クラス SampleReceiver を注入してみましょう。

クラスの関係性としては、下の図のようになります。SampleObjectControllerクラスはSampleReceiverクラスを直接持たずに、IReceiver のみに依存する作りにします。

IReceiver.cs
public interface IReceiver
{
    void Receive();
}
SampleReceiver.cs
using UnityEngine;

public class SampleReceiver : MonoBehaviour, IReceiver
{
    public void Receive()
    {
        Debug.Log("SampleReceiver called!");
    }
}
SampleObjectController.cs
using UnityEngine;

public class SampleObjectController : MonoBehaviour
{
    private IReceiver _receiver; // 何らかのReceiverクラス
    private void Start()
    {
        _receiver.Receive();
    }

    private void Update()
    {
        
    }
}

例として、SampleObjectController がアタッチされたオブジェクトの初期化時に
SampleReceiver の Receive() を呼び出し、「SampleReceiver called!」というログを表示してみましょう。

Scene context の作成

Scene context は Sceneに上のオブジェクトに対して依存性を注入する役割を持っています。

ヒエラルキーウィンドウから「Zenject」→「Scene Context」を選択し、作成。

作成されたオブジェクトには Scene Context コンポーネントがアタッチされているので、こちらに依存性注入を行ってくれる Installer を指定します。

Mono Installer の作成と依存注入設定

Mono Installer は、インターフェースクラスを持ったクラスに対して具象クラスを注入することができます。

プロジェクトウィンドウから「Zenject」→「Mono Installer」を選択し、Installerに名前をつけて適当な場所に保存。SampleMonoInstaller.cs とします。

作成されたSampleMonoInstaller.csは、MonoInstallerのテンプレートになります。
InstallBindings()の中に注入対象のクラスを書いていきます。

注入対象を記述した SampleMonoInstaller.cs
using Zenject;

public class SampleMonoInstaller : MonoInstaller
{
    public override void InstallBindings()
    {
        Container // IReceiverインターフェースにSampleReceiverクラスを注入
            .Bind<IReceiver>()
            .To<SampleReceiver>()
            .FromNewComponentOnNewGameObject()
            .AsTransient();
    }
}

また、注入先の SampleObejctController クラスにアノテーションを付けます。

依存を注入した SampleObjectController.cs
using UnityEngine;

public class SampleObjectController : MonoBehaviour
{
    [Inject]
    private IReceiver _receiver; // Zenjectによって依存注入されたReceiverクラス
    private void Start()
    {
        _receiver.Receive();
    }

    private void Update()
    {
        
    }
}

最後に、InstallerクラスをSceneContextオブジェクトにアタッチし、
SceneContextコンポーネントのMonoInstallerにSceneContextオブジェクト自身を登録しましょう。

全ての設定がうまく行っているかを確認することができます。
Shift+Alt+V を押すと、All scenes validated successfully というログが表示されます。
Zenjectを使ううえですごく便利な機能なので、度々利用しましょう。

何か適当なオブジェクトに対してSampleObjectController.csをアタッチしてプレイモードに移ると、具象クラスで指定していたログが表示されます。

何が嬉しいのか

SampleObjectControllerクラスが持つ_receiveプロパティには、Installerで設定した依存、すなわちSampleReceiverクラスのインスタンスが自動的に注入されます。
もし依存注入を利用しない場合、SampleReceiverクラスのインスタンスを作成して_receiverプロパティに代入する必要がありますが、これはUnityにおいては少し手間です。

SampleObjectControllerを持っているオブジェクトを沢山作成するような場合もInjectは有効です。今回はSceneInstallerを作成したので、シーン内に存在するIReceiverを知りたいオブジェクト全てに注入されます。

クラス設計上の利点

Zenjectのコンテナ上でインスタンスが管理されるため、Injectによる依存性注入を利用したクラスは、依存先のクラスがいつ初期化されたか、いつ破棄されるかを気にする必要がありません。

また、Zenjectを使う場合、インターフェースを介した外部クラスの呼び出しが基本の書き方になります。インターフェースのみに依存する書き方は疎結合を保つので、自ずとSOLID原則を重視した設計を実現しやすくなるでしょう。

Discussion