🎄teratailクリスマスプレゼントキャンペーン2024🎄』開催中!

\teratail特別グッズやAmazonギフトカード最大2,000円分が当たる!/

詳細はこちら
MVP

MVP(Minimum Viable Product)とは、「必要最低限の機能を兼ね備えた製品」を指します。企画書などを完成させる前に、とりあえず製品を形にする方法です。プロトタイプなどで一旦アウトプットさせることにより、無駄なコストや時間を削減できます。

Unity

Unityは、Unity Technologiesが開発・販売している、IDEを内蔵するゲームエンジンです。主にC#を用いたプログラミングでコンテンツの開発が可能です。

意見交換

クローズ

2回答

1743閲覧

UnityでのMVPパターンの実装手段について

退会済みユーザー

退会済みユーザー

総合スコア0

MVP

MVP(Minimum Viable Product)とは、「必要最低限の機能を兼ね備えた製品」を指します。企画書などを完成させる前に、とりあえず製品を形にする方法です。プロトタイプなどで一旦アウトプットさせることにより、無駄なコストや時間を削減できます。

Unity

Unityは、Unity Technologiesが開発・販売している、IDEを内蔵するゲームエンジンです。主にC#を用いたプログラミングでコンテンツの開発が可能です。

0グッド

0クリップ

投稿2023/03/23 21:24

編集2023/03/23 21:31

0

0

テーマ、知りたいこと

Unityで画面などを作成する際にMVPパターンのが紹介されている記事をよく読んでおり、対応手段について意見をお聞きしたいです。
知見がそれほどあるわけではないのでお手柔らかにお願い致します。

下記に実装パターンを記載し、自分の感想も記いたしますのでMVPパターンから外れているか、このようにすれば良いのではないかなど気になった箇所の指摘をいただきたいです。

下記は「人の情報」を表示する実装の例
(後々リスト化表示などもしたい)

実装1

流れとしては

ModelクラスはPersonというマスターデータ情報を持っている
Viewクラスは表示用で表示に必要なパラメータクラスを渡して表示を一括更新する
PresenterクラスはModelの情報からViewの表示用パラメータクラスを作成してViewに渡す
※Viewでは「NickName」というPersonマスターデータにはない表示項目があり、View表示用パラメータで表示用に設定している

●「人」データ(マスターデータのような不変のデータ)

C#

1public class PersonData 2{ 3 public string name; 4 public int age; 5 public float height; 6 public float weight; 7}

●Modelクラス

C#

1public class TestModel 2{ 3 public PersonData personData; 4}

●Viewクラス

C#

1public class TestView : MonoBehaviour 2{ 3 // Viewパラメータクラス 4 public class ViewData 5 { 6 public string Name { private set; get; } 7 public string NickName { private set; get; } 8 public int Age { private set; get; } 9 public float Height { private set; get; } 10 public float Weight { private set; get; } 11 public ViewData(PersonData data) 12 { 13 this.Name = data.name; 14 this.NickName = "nickname_" + this.Name; 15 this.Age = data.age; 16 this.Height = data.height; 17 this.Weight = data.weight; 18 } 19 } 20 21 [SerializeField] private TextMeshProUGUI nameText; 22 [SerializeField] private TextMeshProUGUI nickNameText; 23 [SerializeField] private TextMeshProUGUI ageText; 24 [SerializeField] private TextMeshProUGUI heightText; 25 [SerializeField] private TextMeshProUGUI weightText; 26 27 28 public void ReDisplay(ViewData viewData) 29 { 30 // 色々表示 31 } 32}

●Presenterクラス

C#

1public class TestPresenter : MonoBehaviour 2{ 3 private TestModel model; 4 public TestView view; 5 6 void Start() 7 { 8 // Modelの情報からViewで表示するパラメータを作成してViewに渡す 9 var viewData = new TestView.ViewData(this.model.personData); 10 this.view.ReDisplay(viewData); 11 } 12}

実装2

流れとしては

ModelクラスはView表示用インターフェースを実装した「PersonInfo」を持っている
Viewクラスは表示用で表示に必要なパラメータインターフェースを渡して表示を一括更新する
PresenterクラスはModelの持っているView表示用インターフェースを実装した「PersonInfo」をViewに渡す
※Viewでは「NickName」というPersonマスターデータにはない表示項目があり、View表示用インターフェースで設定している

●「人」データ(マスターデータのような不変のデータ)

C#

1public class PersonData 2{ 3 public string name; 4 public int age; 5 public float height; 6 public float weight; 7}

●Modelクラス

C#

1public class TestModel 2{ 3 private PersonData personData; 4 public PersonInfo personInfo; 5 6 public TestModel() 7 { 8 // personData取得する(略) 9 personInfo = new PersonInfo(this.personData); 10 } 11 12 public class PersonInfo : TestView.IViewData 13 { 14 public string Name => this.personData.name; 15 public string NickName => "nickname_" + this.personData.name; 16 public int Age => this.personData.age; 17 public float Height => this.personData.height; 18 public float Weight => this.personData.weight; 19 20 public PersonData personData; 21 public PersonInfo(PersonData data) 22 { 23 this.personData = data; 24 } 25 } 26}

●Viewクラス

C#

1public class TestView : MonoBehaviour 2{ 3 // View表示用インターフェース 4 public interface IViewData 5 { 6 public string Name { get; } 7 public string NickName { get; } 8 public int Age { get; } 9 public float Height { get; } 10 public float Weight { get; } 11 } 12 13 [SerializeField] private TextMeshProUGUI nameText; 14 [SerializeField] private TextMeshProUGUI nickNameText; 15 [SerializeField] private TextMeshProUGUI ageText; 16 [SerializeField] private TextMeshProUGUI heightText; 17 [SerializeField] private TextMeshProUGUI weightText; 18 19 20 public void ReDisplay(IViewData viewData) 21 { 22 // 色々表示 23 } 24}

●Presenterクラス

C#

1public class TestPresenter : MonoBehaviour 2{ 3 private TestModel model; 4 public TestView view; 5 6 void Start() 7 { 8 this.view.ReDisplay(model.personInfo); 9 } 10}

実装1、実装2の違い

●実装1

View表示用パラメータはPresenterがModel情報をもとに作成する
ModelはPersonという情報のみ持っている
ViewにはPresenterが作成したView用パラメータを渡す

●実装2

View表示用パラメータはModel内でインターフェースを実装したクラスを作成して対応している
ModelはPersonという情報とView表示用インターフェースを実装したPersonInfoというクラスのインスタンスを持っている
ViewにはPresenterがModelから取得したView表示用インターフェースを実装したクラスのインスタンスを取得して渡す

●実装1についての感想

ModelとViewが完全に分かれているで分担しやすい
仮にPerson情報が100件ありリスト表示化するなどの対応になってソートで表示が変わることになったらView表示更新用にView表示用パラメータクラスのインスタンスを100個分作り直す必要がある
→ソート毎にView表示用パラメータを作成する必要がある

●実装2についての感想

Modelクラス内にView表示用インターフェースを実装したクラスがあり完全に分かれていはいないように思える。
仮にPerson情報が100件ありリスト表示化するなどの対応になってソートで表示が変わることになったらView表示更新用にView表示用パラメータクラスのインスタンスを100個分作り直す必要がない
→ソートしてもインターフェースを実装したクラスのインスタンスのリストをソートすればよいだけなのでView表示用パラメータを都度作成ししなくてよくなる。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

回答2

#1

mingos

総合スコア4202

投稿2023/03/24 14:46

編集2023/03/24 14:48

UnityにおいてはUniRxを前提としたMV(R)Pを前提とした情報が多いように思います。
多くの場合、UniRxをモデルに使用する事でより便利になります。
提示のコードではUniRxを使っていないので、UniRxを使用しないという前提の話で進めます。
その場合であってもモデルが自身の値の変化をコールバック(delegateなど)で外部=Preseterに伝える必要があるでしょう。

UniRxを使ったMVPの例として以下の記事が参考になります。
Web出身のUnityエンジニアによる大規模ゲームの基盤設計

MVPの実装の話になるといろいろな手法が出てくるため、とっちらかる印象があります。
そこでMVPの概念的な話だけに限定すると参考記事の通り、以下の定義で同意が得られると思います。

イメージ説明

この記事のMVPパターンのところを読めば終わっちゃう話なんですが、自分なりに解説してみます。

実装1のモデルは仕事をしなさすぎる印象です。

cs

1public class TestModel 2{ 3 public PersonData personData; 4}

これでは何もしてません。モデルである必要がありません。
こんなんだったら直接PresenterにPersonDataをフィールドとして持たせたほうがマシなくらいです。
モデルへの変更をsetterに限定し、その際は値が変更されたことを外部へ通知できるようにdelegateを用意しておいて、presenterがモデルの値を監視できるようにすべきかなと思います。

cs

1public class TestModel 2{ 3 public PersonData pesronData {private set; get;} 4 5 public delegate void OnValueChangedListener(PersonData personData); 6 7 OnValueChangedListener _onValueChangedListener; 8 9 public void SetOnValueChangedListener(OnValueChangedListenerlistener) { 10 _onValueChangedListener = listener; 11 } 12 13 public void SetPersonData(PersonData personData) { 14 this.personData = personData; 15 // 値の変更を通知 16 _onValueChangedListener?Invoke(personData); 17 } 18}

実装2はおっしゃる通りモデルがビューの事まで認識してしまっているのでNGでしょう。
モデルは自分の事というか、値の管理のみに徹するべきです。
もっといえば、ビューがそもそもこんなデータ構造にすべきではない。
素直にPersonDataを受け取って表示処理をすればいいだけです。

ケースバイケースですが、少なくとも提示のコードだけで見れば無駄に複雑にしているだけに見えます。
こんな形でもでいいでしょう。

cs

1public class TestView : MonoBehaviour 2{ 3 [SerializeField] private TextMeshProUGUI nameText; 4 [SerializeField] private TextMeshProUGUI nickNameText; 5 [SerializeField] private TextMeshProUGUI ageText; 6 [SerializeField] private TextMeshProUGUI heightText; 7 [SerializeField] private TextMeshProUGUI weightText; 8 9 10 public void ReDisplay(PersonData personData) 11 { 12 // 色々表示 13 } 14 15} 16

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

#2

退会済みユーザー

退会済みユーザー

総合スコア0

投稿2023/03/25 01:19

ご回答いただきありがとうございます。
ミニマムのコードでこんな感じというのが表せれば良いと思ったので簡素なコードになってしまいました。
実装1のModelが仕事しなさすぎというのもミニマムで書いてしまったがゆえにそのような回答をいただくことになってしまいました。

View表示用のデータを作成したほうがいいのかなと思った理由は3つあります。

1、ViewにModelが保持しているデータ(特にデータクラスのインスタンスなど)を渡してしまうと「View側でデータを更新できるという状態」を作り出してしまうためそれをできなようにするため。
2、View作成者はViewの画面と「このような情報が必要ですよ」というのを定義しておけばよい、というようにしておけば色々作業を分けれるのかなと思っている。
3、仕様の更新などによりViewの表示情報でPersonDataの情報をもとに違うマスターデータを参照してその値を表示する必要がある、などあった際にその処理をViewでやらせたくない(最初からView表示用情報に定義して作成し、Viewの描画更新時に渡すようにする)

そして、
やはりModel側にViewの情報をもたせるのはそもそもパターンの思想から反しているという感じなのですね。
ご回答いただきありがとうございました。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

最新の回答から1ヶ月経過したため この意見交換はクローズされました

意見をやりとりしたい話題がある場合は質問してみましょう!

質問する

関連した質問