なにこれ
- GUIソフトウェアデザインパターンとしてはMVCモデルが有名だし、大学でもMVCモデルは教えることが多い
- しかしWeb開発やスマートフォン開発に元祖のMVCモデルを適用とすると無理が生じる
- Webおよびスマートフォンに適した開発モデルを考えてみる
元祖MVC
元祖のMVCモデルはSmalltalkの世界から生まれたらしい。
元祖の定義によると
- Model...ドメイン領域のデータと振る舞い
- View...ユーザーに対する視覚表現
- Controller...ユーザーの操作を受け取りModelやViewに命令を送る
というのがMVCモデルだそうだ。ユーザーに見えているのはViewである。元祖MVCモデルのソフトウェアは次のように動作する。
- Viewにユーザから入力がある
- ViewはControllerにユーザーからの入力を通知する
- ControllerはViewから受け取ったユーザーからの入力を元に適宜Modelの必要な処理を呼び出す
- ModelはControllerから呼び出された処理を順次実行する
- Modelが処理をした結果Modelが持っているデータが変わることも有る
- ModelはViewに表示するべき情報が変化したときそれをViewに通知する
- Viewは通知を受けてModelから情報を受け取り、自分自身を書き換える
- Controllerはユーザーからの入力に応じて直接Viewにたいして書き換えを指示することもある
6〜7は一般にObservableデザインパターンによって実現されるものである。
元祖MVCが目指そうとしていること
一つは外観と内部実装の切り離し。データとデータに対する処理と入力を受け付ける処理と状態変化を外観に反映する処理がごちゃまぜになっているのは良くない。
Modelは再利用したい。Viewは自由に差し替えられるようにしたい。
- バグ取りしにくい
- 読みにくい
- アプリケーション固有の処理は切り離して再利用できるようにしたい
- アプリケーションの見た目を変えたらそれ以外の処理の部分も書き換えなければいけなくなるのは面倒臭い
内部処理を切り離すことによってその部分は外観に依存しなくなるのでテストをやるのが楽になる。
- UI処理の泥臭い部分が排除されているので
元祖MVCについて留意するべき点
- ViewがModelへの参照を持つことはなんの問題もない
- そもそもViewがModelへの参照を持たないと値を取ってくることができない
- 元祖MVCはModelの再利用を目的としていて、Viewが他の何かに依存することは特に問題にならない
- アプリケーションの状態変化によるViewの書き換えはControllerによって行われるものではない
- ControllerからViewの書き換えが行われることはあるが、基本的にはModelがViewに通知してViewがModelからデータを取ってくるという形を取るべき
- ViewとControllerにはある程度依存性を認める
- GUIにおいてViewとControllerは不可分の存在であることが多いので、ViewとControllerを分けることは余り考えないこともある
- テキストボックスなどはユーザーに文字列情報を提供するViewでありながらユーザーからの入力を受け付ける存在でもある
- ModelからView/Controllerへの依存が起きないようにする
- Modelの再利用性が失われる
- 逆は大いに構わない、というかそうしないとアプリケーションを作れない
- GUIにおいてViewとControllerは不可分の存在であることが多いので、ViewとControllerを分けることは余り考えないこともある
MVC2
WebはステートレスなのでObservableパターンを使ってModelの変更をViewに通知することができない。そこで生まれたのがこの設計手法。
(Ajax的なことすればできるのかもしれないけど、そのあたりは勉強不足でよく知らない)
Strutsを例にMVC2について考えてみる。
ユーザからの入力をJavaServletが受け取る
- Servletはユーザからの入力を解析し適宜必要なアプリケーションロジックを呼び出す
Servletからの要求はアプリケーションクラスが引受け、適宜必要なデータをJavaBeansから取ってきてJavaBeansに実装されている処理をする
- 処理が終わった所でServletは対応するJSPを呼び出す
- JSPは呼び出されるとアプリケーションクラスから必要なデータをとってきて整形し表示する
ServletがController、JavaBeansがModel、JSPがViewに相当する。Modelの変更をViewに通知する手段がないため、それをControllerにやらせているのが元祖MVCとの違いである。
亜種的実装として、ControllerがModelからデータをとってきてViewに渡す、という方式もある。
MVC2において気をつけるべきこと
- Controllerを太らせない
Model=データ置き場という認識になってしまい、Modelに本来実装するべきデータに付随する固有の処理までControllerに書いてしまうとControllerが異様に肥大化してしまう。
貧弱なModelのことを貧血ドメインモデルなどというようだ。
Controllerはできるだけ薄いほうがよい。仕事をModelに振り分け、その結果を元にViewを組み合わせるのがControllerの仕事である。
とまで書いて、太ったControllerというのはトランザクションスクリプト的なのではないかと思った。あるデータが有って、そいつに対してどういう処理をするのかをつらつらと書いていく感じ。
一方で痩せているControllerというのはドメインモデル的だなと。データとそれに対する処理がカプセル化されていてオブジェクト指向的っちゃオブジェクト指向的。
- Controllerを太らせない事を考えるあまりにModelが太ってしまったら
太ったModelも結局保守性が下がることに変わりはない。そのため例えば特定の用途でしか使われずに再利用の必要性が少なそうな処理についてはControllerに書いてしまってもいいかもしれない。
あるいは永続化の必要がないModelは別の扱いにしてControllerと永続化されているModelの間においてしまうのも手か。
MVCが抱える問題
MVCではプレゼンテーションロジックを書く場所がない。すなわちModelの変化に合わせてViewの見た目そのものを変更したいとき、その見た目の情報をどこに持てばよいのか、という問題が起きる。Modelは本来見た目には関与するべきではないし、ViewがそれをやるにしてもViewが見た目に関する情報を保持しなければいけなくなってしまう。これはとくにWebアプリケーションで致命的である。WebアプリケーションにおけるViewは短命であり基本的に使い捨てである。なので見た目に関する情報を保持させるにはちょっと不向きである。
またここ最近はクライアントのリッチ化が進んでいるが、クライアントがリッチになればなるほどViewとControllerは密接になっていく傾向があるように思える。(高機能なUIコンポーネントが増えてくる)
リッチクライアントの世界ではViewとControllerを分ける意味があるのか、という疑問が出てくる。
MVP
MVCの亜種であり、MVCが抱える問題を解決するための手法。
- Model...MVCのModelと同じ。純粋なアプリケーションロジックとデータを持つ
- View...ユーザーからの操作を受け付け、また画面の表示も担当する。正当なMVCのようにViewはModelを監視してもいいが必ずしもそうする必要はない。
Presenter...Viewから入力を受け取ってModelに渡すのはMVCと同じ。Presenterはそれに合わせてPMで言うところのPresentation Modelみたいな仕事もできる。つまりViewの見た目を制御するようなロジックを保持することができて、Viewの見た目をいじることができる
MVPの設計方針としては大きく2つあって
- パッシブView
- 監視Controller
が挙げられる。
View はできるかぎり素朴であるべきで、プレゼンテーションロジックはPresenterに含まれるべきであるというのがパッシブView。
またプレゼンテーションロジックとして書かれるべきなのはViewの中の宣言的なロジックでは要件を満たせないときに限るべきというのが監視Controllerである。
具体的な実装の話をすれば、ViewがModelを監視するものを監視コントローラ、しないものをパッシブビュー呼んで良い。
PresenterからViewを更新するときはViewの実装にPresenterが依存しないようにするためにインタフェースなどを挟むようにする。
PM
ViewとModelの間にPresentation Modelを置いて、そこに見た目に関する情報をもたせる手法。MVPと同じくMVCの問題を解決する手法。
MVPはMVCの抱える問題を直接Viewを書き換えるものを作ることで解決したが、PMではPresentation Modelを間に挟ませるようにする。
MVCではViewはModelを監視してたんだから、その流儀に則るべきだろう、というところだろう。
ViewはModelの代わりにPresentation Modelを監視する。Presentation ModelはModelからアプリケーション固有のデータを引っ張ってくる。
また自分自身でViewの見た目を定義する情報を保持していて適宜Viewに渡す。
- ユーザからの入力をControllerが受け取る
- Controllerはユーザからの入力を解析しModelから適宜必要なロジックを呼び出す
- Modelは処理を行い、必要に応じて自分自身の状態を書き換える
- ModelはPresentation Modelに変更を通知する
- Presentation ModelはViewに変更を通知する
- ViewはPresentation Modelから出力に必要な情報をとってこようとする
- Presentation ModelはViewからの要求に応じてModelから必要なデータをとってくる
ModelからPresentations Model、それからPresentation ModelからViewへの参照を張ることはできないので、例えばインターフェースなどを使って間接的に参照するような設計が必要だろう。
PMの課題点
ViewとPresentation Modelは緊密に連携するため委譲のためのコードをいっぱい書かなければいけないかもしれない。
MVVM
PMモデルに似ている。WPFなどで推奨されているため、その界隈では常套手段になっているようだ。
- Model...MVC、そしてPMと同じくアプリケーション固有のロジックをもたせる
- View...アプリケーションの見た目を定義する。入力の受付も行う。少しの見た目に関するロジックを持たせても良い。ただし状態は持たせるべきではない
- View Model...見た目に関するロジックと状態を持つ
Viewにはなるべくコードを書くべきではなく、見た目はXAMLなどで定義するべきである。
Viewを薄くしてView Modelをレンダリングするための装置ぐらいに考えるのが良い。
PMモデルに似ているため、やはりViewとView Modelの緊密な連携が重要である。ただしView ModelはViewを参照してはならない。ViewはView Modelを参照しても良い。
WPFでの実装ではViewからView Modelへの参照はリフレクションを使う。逆はイベント呼び出しをするようだ。ただこれらはデータバインドという仕組みによって遮蔽されているのでそれを意識しなくてもいいようになっている。
ViewModelはViewを意識しますが、その実装について何も知らなくてもよいし、知るべきではない。
View Modelは何かというと表示に関するロジックや状態を、Viewに依存しない形で実装。また、View ModelはModelへの操作をコマンドという形で提供。
このおかげでViewとView Modelはお互いにどのような構造になっているのか知らなくてもお互いを叩くことができるっぽいですね。
- WPFではそうなっているけど、他のプラットフォームにはそんな便利な機構が用意されていないので、MVVMで実装しようとするとそりゃ大変なことに……
- android-bindingはまだ中途半端な感じだし
PMとMVPの違いを端的に考える
この2つの違いは端的に言ってしまえば、PMはViewとPresentation Modelの間の強力なバインドを前提としているということだろう。バインド機構がなくてもできなくはないが、コードが無駄に増えて利点が失われる。バインドという形で相互の通信が遮蔽されることによって、開発者のコーディングによって結合度が上がってしまうという状況を避けられる、という設計のようだ。
サーバサイド
サーバサイドに関してはStrutsやRailsのようにMVC2が採用されていることが当たり前になっている。というのもMVC2ではViewとModelが全く関係のない存在になっている。トラディショナルなMVCではViewとModelは綿密に連携するが、サーバサイドなシステムで最終的に吐かれるViewは使い捨てで、静的である。つまりViewとModelの連携を実現できない。
クライアントサイド
ここ最近はクライアントサイドのJavaScriptフレームワークもじゃんじゃか出てきてるみたいだが、これらはMVCで書かれてたり、PMで書かれてたりするようである。MVC2は逆に採用しにくい。というのもMVC2ではModelからリアルタイムにイベントを発火させることができない。リアルタイムに変化することを求められるクライアントサイドアプリケーションには向いていないように思われる。
Android
基本的にクライアントサイドと同じように考えればよいのではないかと思う。特にTwitterクライアントのユーザーストリームはいい例だ。Twitterから送られてくるTweetによってModelでイベントが発火しそれによってViewが更新される、というパターンを実現するにはMVCかMVPかMVVMを使うのが妥当だろう。ただMVVMはAndroidに標準で強力なデータバインド機構が存在しないこともあり導入は難しいかなという気がする。
(Contextとかいうヤツのせいで余計にその辺りが難しくなっている……)
ただ、見た目とそれ以外に分けるだけでもUIへのバインドとリスナー登録とネットワーク通信とロジックがごっちゃになった気色悪いActivityを生産しなくて済むのでだいぶメンテしやすくなるし、テストも書きやすくなると思う。
UIへのバインドとかセッタ・ゲッタの羅列はデータバインドが出来れば一気に消し去れそうな気もする。
(ちょっと調べてみたところUIへのバインドに関しては「依存性の注入」という技術を応用すれば消し去れそうである。試してみたい。)