Vcontainerを利用してMVPパターンを表現してみた
はじめに
普段からインターン先や個人で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上の状態
midra-lab.notion.site/MidraLab-dd08b86fba4e4041a14e09a1d36f36ae 個人が興味を持ったこと × チームで面白いものや興味を持ったものを試していくコミュニティ
Discussion