はじめに
今回はUnityにおけるMVPの実装方法のうち、Passive Viewを使用したものがどれだけ有用な設計になるか検討していきます。
Passive Viewとは?
Passive Viewとは、Presenterから指示を受けて動くだけのViewです。
監視型Viewとは違って、自分からModelを参照することはありません。
- Modelが変更を通知する
- PresenterがModelの変更をもとに、論理的な画面を操作して、対応するViewを呼び出す
- Presenterから呼び出されたViewは、(論理的な画面構造や条件分岐などは知らずに)ただ言われたとおりにUnityを操作する
という流れで画面に値を出力していきます。
Passive Viewの実装方法
- PresenterがObservableを提供してそれをViewがイベントとして受け取るパターン
- PresenterがViewのメソッドを呼ぶパターン
- Viewが毎フレームPresenterの値を監視するパターン
などが考えられます。
2番目の方法をとると、ViewがMonobehaviourの形で定義されているので、PresenterがUnityに依存してしまいがちで切り分けが難しいです。
3番目の方法をとると、以前の記事でも書きましたがコードのパフォーマンスや可読性が落ちがちです。
したがって、1番目の方法によるPassive Viewを推奨します。
この手法は、ModelとPresenterの接続と同じであることから、統一感の観点から見てもよいです。
Passive Viewの利点
最大の利点は、演出用のこまごましたロジックやデータが全部Presenterに閉じ込められることです。
これによって、Viewが小さくなります。このあたりのことについては以前の記事に書いてるので詳しくは割愛しますが、第二章理論編その1で解説したようなModelでもViewでも扱いづらいややこしいデータが全部Presetnerに入っているので、少なくともViewとModelに関しては分離によるメリットを最大限受けられます。
Passive Viewの問題点
よく指摘される問題点は、Presenterが「ユーザー入力→Model」と「Model変更やその他のトリガー→View」の双方向の処理を一手に担うので、肥大化しやすいことです。その結果、Presenterが複雑すぎてわけがわからなくなり、コードの見通しが悪くなるという欠点があります。
Presenterが肥大化する問題への反論
Passive Viewは、端的に言えば、「Model・Viewが扱いたくない情報を全部Presenterに押し込む方式」です。
したがって、画面の複雑さはそのままPresenterの複雑さに直結します。
Presenterの肥大化してコードの見通しが悪くなるからさらにコードを整理しようという動きもあるのですが、ここで私は、UnityにおけるPresenter肥大化邪悪論に対して、少し反論したいと思います。
1. Presenterが肥大化する方がマシ
Presenterに押し込まれる業務は、Unityを用いた表示のためのコードでもなく、また純粋なゲームのロジックでもありません。
Presenterのコードと言うのは、演出のために画面ごとに作られるその画面限りの使い捨て変数と処理の集合体からUnity依存を抜いたものになります。Presenterから処理を抜き出して分割したとしましょう。しかし「その画面限りの使い捨て変数と処理」をModelやViewのどこに持っていってもPresenterにあるときよりも苦しくなるのは明らかです。
- 仮にPresenterの複雑性がModelに流出した場合は、全画面に対してその複雑性への対処を求めざるを得ません。
- 仮にPresenterの複雑性がViewに流出した場合、Viewが大きくなることの苦労はたびたび説明しているとおりです。
2. そこまで複雑化しない
確かに複雑なシーンにおいてはPresenterはその画面に起こる演出と入力の数だけ肥大化します。しかし、Presenterは1シーンに1つという特徴があります。つまり、肥大化にはおのずから限度があるのです。そこまで極端には肥大化しないといっていいでしょう。
WebにおけるFat Controllerが問題視されるのは、ユーザーの入力のすべてを受け取り処理するルーティング部分が大きくなるからです。高々1画面の複雑性は、割とどうとでもなる範囲であることが多いです。
3. 肥大化しても大したことない
Presenterは、孤独な存在です。
なぜなら、ViewもModelも、他のPresenterもPresenterのことを知らないからです。
これはつまり、Presenterの複雑性はあくまでそのPresenter内部のみに閉じ込められることを意味します。
したがって、Presenterが肥大化したところで、影響するのはそのPresenterを書こうとしているとき、改修しようとするときだけなのです。ほかの場所に書くより圧倒的にマシだといえるでしょう。
これは、複雑なオブジェクトもprivate変数が多くてpublic変数が少なければ、(改修の手間はさておき)中身がどれだけ複雑でも外部の利用者は複雑性を気にせずに使える、というオブジェクト指向プログラミングにおけるカプセル化の考え方と同じです。
4. 1画面のPresenterを分割すると演出の自由度が下がる
Presenterは演出用の一次変数・処理保管の役目を持っています。
そして、ゲームにおいてこれらの演出は互いに関連しやすいです。
例えば、アニメーションなどでは、ばらばらにアニメーションするのではなく、画面の要素全体がタイミングを合わせて動いた方が見た目がかっこよかったりしますよね。
このような処理を実装しようとする場合、画面の全要素においてPresenterに保管されている「アニメーションタイミング情報」を知る必要があります。もしも1画面1Presenterの場合には1つのインスタンスに全部詰め込まれているので簡単に取得できますが、1画面複数Presenterの場合には、結局それぞれのPresenterのカプセル化を破壊して内部の変数を参照することになるでしょう。
そもそも、「演出用の一次変数を雑に全部まとめたものの集合体」が明確にカプセル化できるようなデータの塊の単位を持つと考えてカプセル化を適用しようとすること自体が誤りと言えるでしょう。
Presenter同士が交流する方法は、MVPモデルでは定義されていないことも相まって、一度画面の中でPresenterを分けてしまえば、PresenterAが担当している画面パーツと、PresenterBが担当している画面パーツの連携は非常に難しくなります。プログラムの都合で演出の自由度、すなわちゲームの見た目の豪華さを削るのは最小限に抑えたいです。
5. Modelの処理をPresenterが吸い上げていた場合、リファクタリングや開発規模の拡大で気づく可能性が高い
ModelはPresenterを知らないことから、Presenterの処理は基本的に再利用できません。これは、複数画面で処理を使いまわそうとしたときに、Presenterにその処理がある(つまり、論理的な画面を扱うというPresenterの責務を逸脱してる悪い状況)ならば、再利用が困難になり、リファクタリングの必要性に気づくということです。
したがって、一時的に肥大化したPresenterも(もちろん、コピペで解決などの無茶をしなければ)そのうちModelと分割が出来るはずだといえます。
おわりに
ModelとViewをきれいに分割する方法として、Presenterを介する手法は、優れたUnityの設計だと考えています。
個人開発レベルのゲーム規模であれば、ここまでやれば安心だろうと言えるラインだと思われます。
設計について悩む人は、ぜひ試してみてください
もちろん、今回だけでは具体的な実装は難しいので、実践編や続編シリーズにて詳しくPassive Viewの実装方法について解説していきます。