🙌

Vcontainerを利用してMVPパターンを表現してみた

2023/03/13に公開

はじめに

普段からインターン先や個人でUnityを利用して開発することが多いのですが、
設計の深く精通しているわけではないので、間違いなどがありましたらご指摘いただけると幸いです。

MVPパターンとは

MVPパターンとは、Model-View-Presenterの略で、Model(パラメータなどの値を持つクラス)とView(画面の表示, 入力などを行うクラス)の間にPresenterを挟むことで、
ViewとModelの依存関係をなくすことができるデザインパターンです。

Vcontainerとは

通常、Unityは MonoBehaviour を継承したクラスのみを処理の起点にできますが、VContainerを用いることでオブジェクト同士の参照関係/所有関係を自由に構築したり、純粋なC# クラスのエントリポイントをつくることができます。これはUnityのコンポーネントに依存する部分・見た目の部分と、その他純粋なロジックを分離する設計の手助けになります。
このような制御の反転はIoC(Inversion of Control) などと呼ばれていて、DIコンテナの設計上の利点のひとつです。
引用元 (https://vcontainer.hadashikick.jp/ja/)

こちらをみていただくと間違いありません。

今回のサンプル

(https://github.com/eiei114/VcontainerMVPsample)

MVPパターンを表現してみた

今回はUniTaskを利用して非同期処理を行うことを想定しています。

Model

   public class Model
    {
        private readonly AsyncReactiveProperty<int> _score = new(0);

        public IReadOnlyAsyncReactiveProperty<int> Score => _score;
        
        public void AddScore()
        {
            _score.Value++;
        }
    }

Modelは、今回はスコアを持っているだけです。
UniTaskのAsyncReactivePropertyを利用して、値の変更を通知することができます。
スコアを1増やすという関数を用意しています。

View

    public class View : MonoBehaviour
    {
           [SerializeField]private Button _button;
           [SerializeField]private Text _text;
    
           public UnityEvent ButtonClick => _button.onClick;
           public string Text { set=> _text.text = value;}
    }

ボタンを押したという通知を送るためにUnityEventを利用し、textを変更するためにstringのセッターを用意しています。

Presenter

    public class Presenter : IDisposable,IInitializable
    {
        private readonly CancellationTokenSource _cts = new();
        private readonly Model _model;
        private readonly View _view;
        
        [Inject]
        private Presenter(Model model, View view)
        {
            _model = model;
            _view = view;
        }

        public void Initialize()
        {
            Debug.Log("Presenter.Initialize");
            _view.ButtonClick.AddListener(_model.AddScore);

            _model.Score.Subscribe(x => _view.Text = x.ToString());
        }
        
        public void Dispose()
        {
            _cts.Cancel();
            _cts?.Dispose();
        }
    }

IDisposable,IInitializableを実装しています。IDisposableはPresenterが破棄されたときに呼ばれるようにしています。
IInitializableはPresenterに依存先が注入され初期化されたときに呼ばれるようにしています。(こちらに関しては後ほど詳しく説明します。)

Initializeメソッドでは、Viewのボタンが押されたときにModelのスコアを増やすようにしていてスコアが変更されたときにViewのテキストを変更するようにしています。

Disposeメソッドでは、CancellationTokenSourceをキャンセルと破棄をしています。

LifeTimeScope

    public class RootLifeTimeScope : LifetimeScope
    {
        [SerializeField]private View _view;
        
        protected override void Configure(IContainerBuilder builder)
        {
            builder.RegisterEntryPoint<Presenter>();
            builder.Register<Model>(Lifetime.Singleton);
            builder.RegisterComponent(_view);
        }
    }

RootLifeTimeScopeは、VcontainerのLifeTimeScopeを継承しています。
Configureメソッドでは、依存性の注入をしたいクラスや注入するクラスの登録を行います。

builder.RegisterEntryPoint<Presenter>();では、Presenterをエントリポイントとして登録しています。
この登録方法だとVcontainerの指定のinterfaceを実装することでMonoBehaviourの持つstartやawakeなどのエントリポイントを
通常のC#のクラスと同じように利用することができるようになります。

builder.Register<Model>(Lifetime.Singleton);では、Modelをシングルトンで登録しています。
Lifetime.Singletonで登録することでこのModelクラスが全て同じインスタンスとして注入できます。
Lifetime.Transientで登録することでこのModelクラスが全て異なるインスタンスとして注入できます。

builder.RegisterComponent(_view);では、Viewを登録しています。
RegisterComponentで登録することで、MonoBehaviourを継承したクラスを登録することができます。

Unity上の状態

MidraLab(ミドラボ)

Discussion