dasukoのブログ

現役エンジニアのブログです。

【C#】DI(Dependency Injection)を使ってみた!(Unityでの実装例あり)

はじめに

今回はDI(Dependency Injection)についてまとめてみたいと思います。

Dependency Injectionとは

Dependency Injectionの意味は依存性の注入です。

Wikipediaにはこのようにあります。

依存性の注入(いそんせいのちゅうにゅう、英: Dependency injection)とは、コンポーネント間の依存関係をプログラムのソースコードから排除するために、外部の設定ファイルなどでオブジェクトを注入できるようにするソフトウェアパターンである。英語の頭文字からDIと略される。

依存性の注入 - Wikipediaより引用

 

Dependency Injectionを使うメリットも含めて簡単に説明すると、AというクラスがBというクラスに依存している場合(AというクラスがB型のフィールドを持っている場合)、Bに変更を加えると、

Aも修正する必要があったり、Aのテストも再度する必要が出てきたりします。

また、Bというクラスのモックを使用してユニットテストをしたい!となっても置き換えることができません。

そのため、AというクラスはBというインターフェースに依存するようにします。

そして、Aを初期化するときにB'というBのインターフェースを実装したクラス型のオブジェクトを渡します。

こうすることで、AはB'に依存していないので、Bインターフェースの振る舞いを後から変更することが容易になります。

また、ユニットテストをする場合でも、モックB''を用意すれば、簡単にAのユニットテストを実行することができます。

C#Dependency Injectionを使ってみる

以下に簡単な実装例を示してみます。

LoggerのインターフェースをModelの初期化時に渡すようにしてました。

// Logger interface
public interface ILogger
{
    // 簡易的なのでログレベルはなし
    void Log(string msg);
}

// Model class
public class Model
{
    private readonly ILogger logger;

    public Model(ILogger logger)
    {
        this.logger = logger;
    }

    public void DoSomething()
    {
        logger.Log("DoSomething");
    }
}

この例だと、Modelが一つしかないので問題ないですが、

クラスが増えてくると、初期化処理を書くのが面倒になりそうです。

そのため、ライブラリを使うといいと思います。

Zenjectを使ってみる

今回はUnity環境(C#)でDI(Dependency Injection)を試して見たいのでExtenjectというライブラリを使うことにしました。

これは元々Zenjectというライブラリがあって、そのリポジトリからフォークされたものみたいです。

GitHub - svermeulen/Extenject: Dependency Injection Framework for Unity3D

MonoInstallerを追加してみる

  1. ヒエラルキータブで右クリックして、Zenject->Scene Contextを選択します

  2. 次にプロジェクトタブでフォルダを右クリックして、Create->Zenject->MonoInstallerを選択してMonoInstallerを作成します。

  3. 作成したMonoInstallerのスクリプトをシーン上のGameObjectにアタッチします。

  4. 次にSceneContextを選択し、インスペクター上でMonoInstallerプロパティのところに+ボタンで新しく行を追加し、

作成したMonoInstallerを追加します。

f:id:dasuko:20201023234109p:plain

  1. Edit->Zenject ->Validate Current Sceneを選択、もしくはControl+Alt+Vを押すことで検証を行うこともできます。

まずはValidate Current Sceneの動作も見たいので、ExtenjectのREADMEにのっていた以下のサンプルを追加してみました。

(文言は"DI Test"に変えてみました)

using UnityEngine;
using Zenject;

public class TestInstaller : MonoInstaller
{
    public override void InstallBindings()
    {
        Container.Bind<string>().FromInstance("DI Test");
        Container.Bind<Greeter>().AsSingle().NonLazy();
    }
}

public class Greeter
{
    public Greeter(string message)
    {
        Debug.Log(message);
    }
}

Validate Current Sceneを実行しても、以下のようなログが出ていて問題なさそうですね。

f:id:dasuko:20201023235630p:plain

これで実行すると、しっかり"DI Test"というログがコンソールに出力されました!!!

f:id:dasuko:20201023235734p:plain

次にインターフェースとそれを実装したクラスを定義して、バイドしてみたいと思います。

using UnityEngine;
using Zenject;

public class TestInstaller : MonoInstaller
{
    public override void InstallBindings()
    {
        Container.Bind<IService>()
            .To<ServiceImpl>()
            .AsSingle();
    }
}

public interface IService
{
    void DoSomething();
}

public class ServiceImpl : IService
{
    public void DoSomething()
    {
        Debug.Log("DoSomething");
    }
}

public class Model
{
    [Inject]
    private IService service;

    public void DoSomething()
    {
        service.DoSomething();
    }
}

次にMonoBehaviourを継承したクラスを作ります。

StartメソッドでDoSomethingメソッドを実行してみました。

using UnityEngine;
using Zenject;

public class DependencyInjectionTest : MonoBehaviour
{
    [Inject]
    private DiContainer container;

    void Start()
    {
        var model = container.Instantiate<Model>();
        model.DoSomething();
    }
}

結果以下のように"DoSomething"がコンソールに出力されました!!!

f:id:dasuko:20201024014457p:plain

参考

依存性の注入 - Wikipedia

ASP.NET Core での依存関係の挿入 | Microsoft Docs

GitHub - svermeulen/Extenject: Dependency Injection Framework for Unity3D

Zenject入門その1 疎結合とDI Container - Qiita