はじめに
前回、ModelとViewがそれぞれ何者であるかに加えて、それらを分離することの利点について書きました。
しかし、現実問題としてModelとViewを分離することには大きな困難を伴います。
今回は、なぜModelとViewがキレイに分離できないのか、具体的な原因の特定を目指します。
ModelとViewが分離できない状況
1. 表示制御のためのデータが増加する
どのようなデータが「表示制御のための一時データ」に該当するか
複雑なゲーム画面を組み立てていくと、ModelでもViewでもないデータが増加します。
これらのデータは「サンプルコードではシンプルすぎて出てこないので解説されにくいけれど、現実的にゲームを組むとどうしても出てきて扱いに困る」という厄介な存在です。
これらのうち、具体的には以下のようなものを「表示制御のための一時データ」と呼ぶこととします。
- アニメーションの秒数を管理するためのフラグ
- マウスを早く動かしたときにのみ動かす演出のために、マウス座標の差分をとっておくための変数
- スナップするスクロールビューにおいて、現在どこのページにいるかを保存しておく変数
- UIの入力モードが複数あるときに、そのどれであるのかを保存しておく変数
このようなデータは、業務用のソフトウェアでは無視できるほど少ないでしょう。ほとんどが演出用であり、省いてしまえばよいのです。しかし、ゲームは演出です。これらの変数が莫大に増加することがほとんどなのです。
さて、これらのデータをどこに記述していくのかについて考えるとなんとModel・Viewのどちらにも書けないということが分かります。
Model側に記録した場合の問題点
Modelにアニメーション状況や表示可否などの情報を記憶させる方式です。
Modelが画面に依存してしまいます。
Modelが画面のレイアウト・演出に合わせて変更に迫られるということです。
これはViewの柔軟性を損なうとともに、Modelの一部のテストにViewが要る状況を生んでしまいます。
これは前回の原則「Modelは純粋にする」に反するコードです。
View側に記録した場合の問題点
Modelから受け取った情報とViewが持っている情報を合わせて演出を出す方式です。
Viewに条件判断が増え、肥大化します。
Unityの起動回数は増え、面倒なテストを何度もする羽目になります。
これは前回の原則「Viewは極限までシンプルにする」に反するコードです。
2. シンプルなViewから受け取る入力は低レベルすぎる
ユーザー入力はUnity、すなわちViewを通して受け取らざるを得ません。そしてユーザーはViewを通してModelを操作しようとするのです。小さなサンプルコードだけでは前回のように「ボタンを押して、画面に表示してハッピーだね!」というようになって終わりなのですが、ユーザーの操作は
- ダブルクリック判定をするために、「直前15フレーム以内にクリックがあったか」を保存しておく変数
- 3回のボタンクリックをすることで初めて意味を持つ入力
- カードをドラッグアンドドロップして別の場所に置くときの入力
など多岐にわたります。そしてこれらの入力は、意味を持つタイミングが「複数のデータ・一次変数から複合的に判断される」という特徴を持ちます。これはボタンによる入力が「押された瞬間に意味が確定する」ものであったこととは対照的です。
さて、それでは上に挙げた例の「直前15フレームの入力の有無」「ボタンクリックが現在何回されているか」「ドラッグ中か否か」などの情報はどこに保存すればいいのでしょうか?
じつはこれもModel・Viewどちらにも書けないのです。
Model側に記録した場合の問題点
Modelが「画面の(x,y)がクリックされたときに呼ぶ関数」などを用意して、低レベル情報を直接受け取る方式です。
この場合、同じくModelがViewを気にしてしまうためModelの純粋性を損ないます。
さらに、この手の関数をテストしようとするとテストケースに「(23,455)をクリックしたときの関数を呼ぶ→マウスの座標(22,444)に移動したときの関数を呼ぶ」と言ったように非常に低レベルなものになります。この(23,455)とかの座標は結局Viewを見ないとダメですし、こんなテストを書くぐらいなら、Viewを起動して動作確認するほうがよっぽど早いですね。つまりこれは、「Modelがテストを書くことを容易にする」というメリットを完全に破壊しているのです。
View側に記録した場合の問題点
Viewが「どこをクリックされたか」などの情報をためておき、ある程度たまったタイミングで、画面から離れた抽象的な意味を持つUI操作としてModelの関数を呼び出す方式です。
この場合、Viewが大きくなり、「Modelの関数がちゃんと呼ばれるか」の検証が非常に面倒です。
Viewはシンプルにするという原則に反するコードです。
3. 「ModelとViewの責務」という言葉を曖昧に使っている
これは1,2とは異なり、構造的にどうしようもないもの、といえるものではありません。
Viewを見た目だ!ぐらいのぼんやりしたイメージ、Modelをデータの入出力だ!ぐらいのぼんやりしたイメージで組み立てると、往々にしてViewがModelの処理を奪い取ります。(ModelがViewを奪うことはあまりありません。というのも、Viewは最小限の状況が一番いい状況だからです)
また、View・Modelの2人体制だと、ViewはModelを参照できるわけですから、Modelをpublic変数から好き勝手操作できてしまいます。
結果としてModelとViewの密結合を生みやすくなります。
ModelとViewの原則、「純粋性」と「シンプルさ」を強く意識したコーディングである程度は防げますが、根本的にModelへの参照をなくしたり、疎結合にしたりする策を講じないとView側でいじってしまうという問題はなくなりません。
おわりに
経験的には、「サンプルコードで書けてるようにキレイにならない!!なんで!!」という叫びの原因のうち、大部分はここにあると思います。また、漠然とViewとModelとX(ControllerなりPresenterなりViewModelなり)を切り分けたいというだけの思考でMVXの設計をするのではなく、「どうしようもないからXへ持っていく。ModelとViewは明確な原則に従って設計する」の方がよい設計になることが多いと思います。次回、今回上げた問題の解決策を探していきます。