MVCについて色々な文献をあたり、色々なことが書いてあったので自分用にまとめた。みなさんは各本などを読んでください。
MVC とはなにか?
どの文献を参照しても共通していること
- ユーザーインターフェースを持つアプリケーションを Model/View/Controller の3つにの部分に分離する
解釈が難しい点
- モデル・ビュー・コントローラの責務と関係
- 特にコントローラの責務
- コントローラとビューの関係性
- 各プラットフォームにおいて、上記の責務をどう実装に落とし込むのか
- ほんとうにビューとコントローラは分離できるのか?
- そもそもビューとコントローラを分離する利点は?
そもそも分離とは?
Model-View-Controller に分離するパターンを見ていくが、クラスに分割するだけが分離に相当するわけではない
- サブルーチン・メソッド
- クラス
- パッケージ
- モジュール
PoEAA における MVC の説明
UIの相互作用を3つの明確な役割に分割するパターンを「モデルビューコントローラ」とよぶ。
ViewとControllerは互いに依存してよい。ViewとControllerはそれぞれ、Modelに依存してよい。
パターンの仕組み
モデルはUIに関わらないドメインについてのデータやふるまいが表現されたオブジェクト。ビューはUIの中でモデルを表現する。ビューは情報の表示だけを責務として負う。コントローラは、ユーザーからの入力を受け取り、モデルを操作して、ビューを適切に更新する。このようにUIはビューとコントローラの協調によって構成される。
プレゼンテーションとモデルの分離
根本的にプレゼンテーションとモデルは異なる関心事を持っている。ビューはどのように良いUIを提供するかということに興味があり、モデルはどんなロジックでデータを取り扱おうかということに関心がある。したがって分離を行い、各関心事に応じて構造化するために分離を行う。
非ビジュアルなオブジェクトは、ビジュアルなオブジェクトよりテストがしやすい。当たり前のことではあるが、プレゼンテーションとモデルを分離することにより、ドメインロジックのテストは容易になる。
プレゼンテーションはモデルに依存するが、モデルはプレゼンテーションに依存しないようにする。こうすることによりモデルを構成するときに、どんなプレゼンテーションから使われるかということを全く意識しなくてすむようになる。また、これによりモデルを利用して新たなプレゼンテーションを追加することが容易にもなる。
複数のウィンドウを持ったクライアントにおける問題
複数のウィンドウを持つリッチクライアントでは、あるモデルを参照する複数のプレゼンテーションが同時に表示される可能性がある。その際、ユーザーが1つのプレゼンテーションを更新したら、その変更は伝播されなければならない。モデルがプレゼンテーションに依存を作らずにこのようなことを実現するためには、オブザーバーパターンの実装が必要になる。プレゼンテーションはモデルのオブザーバーとして動作させ、モデルが変化するごとにイベントが発生して、情報が更新されるされる仕組みを実現する必要がある。
ビューとコントローラの分離
こちらは、プレゼンテーションとモデルの分離に比べると重要度は下がる。例えば、編集可能な振る舞いと編集不可能な振る舞いを同時にサポートをしたいときに、ビューとコントローラを分離したいといったモチベーションが生じる。1つのビューに対して、2つのコントローラを作ることにより対応できるが、通常はそのような分離は行われていない。
プラットフォームによってはUIにおける入力と出力はしばしば不可分であり、GUIフレームワークではビューとコントローラを組み合わせていることが多い。Webインターフェースではコントローラとビューの分離が行われることが多い。
パターンを用いるタイミング
モデルビューコントローラは、プレゼンテーションとモデルおよび、ビューとコントローラの分離に特徴がある。前者については非常に重要で、ほとんどの場合、分離をすべきである。後者についてはわりあい重要ではなく、必要なときにだけ分離をすることを推奨する。ネイティブクライアントにおいて、分離することはほとんどないが、ウェブフロントエンドにおいて、ビューとコントローラを分離することは珍しくない。
POSAにおける MVC の捉え方
UIは複数の見た目を用意したり、特定の人に向けたものを用意する必要があったりと、要求により変更が加わる傾向が強い。アプリケーションのコアはこうしたUIの変更の影響を受けるように作るべきでない。更新頻度の少ないロジックはプレゼンテーションから分離されている方が良い。UIの変更は簡単かつ、局所的であるべきだ。しかし、変更が容易なUI層を実現するためにサービスの品質を落とすわけにはいかない。いつでもアプリケーションの状態を反映したUIを維持すべきであり、状態の変化には素早く反応しなくてはならない。したがって、インタラクティブなアプリケーションは、処理・入力・出力の3つの構成要素に分解できる。これら3つの整合性は、変更を通知する仕組みにより保証していく(この点はMicrosoftの記事のactive型のほうをさしているのかな、と思う)。
アプリケーションのコアはモデルとしてカプセル化して、UIの見た目の振る舞いに関する実装から独立させるべきである。ビューはこうしてできたモデルの一側面を切り取って表示する。コントローラーはユーザーからの入力を受け取り、モデルやビューと協調する。ユーザーがアプリケーションに入力を与える受け口は、もっぱらコントローラーの役割となる。モデルに変更が生じたときには、すべてのビューとコントローラーに変更を伝え、それぞれの状態を適切かつ迅速に更新するようにする。これによりモデル、ビュー、コントローラーの協調を実現している。
MVCは、それぞれのオブジェクトの独立した開発をサポートするために、異なる頻度で変更される傾向があるアプリケーションの責務を分離する。モデルはインタラクティブなアプリケーションのコア機能を定義しまう。したがって、その内部構造はアプリケーション固有のドメインの責務に強く依存する。しばしば、モデルは複数個のドメインオブジェクト[POSA]に分離され、それぞれに責務を持つ。モデルの実装は特定のデータフォーマット、ビューやコントローラーのAPIに依存してはならず、UIが変更されたときにモデルを変更しなければならないようなことは避けなければならない。
アプリケーションのUIに表示される情報の各部分は、モデルのそれぞれのデータを取得して、表示する形式に変換する機能も含めて、ビューのなかにカプセル化する。これにより、モデルなどに影響を与えることなく、ビューに変更を入れることができるようになる。ビューに関する典型的なパターンとして、テンプレート・ビューとトランスフォーム・ビュー[POSA]の2つがある。テンプレート・ビューは、あらかじめ定義されている出力形式にモデルの情報を差し込んであげることにより、ビューを構成する。また、トランスフォーム・ビューは、モデルから取得する各データ要素をレンダリングすることによって、出力を作成する。各ビューは、モデルの状態を操作するために1つないし、複数のコントローラーと紐付いている。またコントローラーはキーボードやマウスなどから入力を受信する。コントローラーには次の3つのパターンがある。
- UIの特定の機能に関連付けられているコントローラー
- 特定のフォームやページからの要求をすべて処理するページ・コントローラー
- モデル上のすべての要求を処理するフロントコントローラー
モデルが様々な機能を持っているとすれば、機能ごとにコントローラーを作るのが一番良いだろう。フォームやページベースのUIを持つ場合はページコントローラーが適切だ。また、アプリケーションがそれぞれのリクエストごとに違ったUIを提供するような場合には、フロントコントローラーが最も有用です。WebアプリケーションにおけるHTTPプロトコルなどがこれに相当します。
コントローラーから発行されるリクエストはコマンドオブジェクト[POSA]にカプセル化され、コマンドプロセッサー[POSA]に渡り実行される。こうすることによりビューとモデルを透過的に更新することができる。さらに、リクエストを第1級オブジェクトとして扱うことができるようになり、アンドゥ・リドゥや実行のスケジューリングなど一般的なアプリケーションの動作をサポートする手助けにもなる。
ワークフロー駆動型のアプリケーションにおいて、どのようなコマンド作成すればよいのか疑念がある場合は、アプリケーション・コントローラーを用いるとよい。これはモデルの内部状態への依存を回避するのに役立つ。ほとんどのアプリケーションでは、複数のコントローラーが同時にアクティブな状態となる。しかしながら、ユーザーの入力は特定のコントローラーで処理される。すべてのコントローラーを単純に束ねるチェイン・オブ・レスポンシビリティ[POSA]により、ある入力がどのコントローラーに割り当てられるべきかをシンプルに解決することができる。
デバイスの低レベルなAPIやグラフィカルなライブラリにアクセスする際に、ラッパー・ファサード[POSA]を使うと、ビューとコントローラーをシステムのプラットフォームから独立した状態にすることができる。またデータ・トランスファー・オブジェクト[POSA]を使うと、ビューやコントローラーがモデルから取得したデータをカプセル化するのに役立ちます。
モデルがUIからの独立性を失わずにMVCの各オブジェクトが効率よく協調するためには、オブザーバ[POSA]を用いて結合する必要がある。コントローラーとビューがオブザーバーとなり、モデルがその対象となる。モデルの状態が変化したら、登録されているビューやコントローラーに通知を行う。通知を受けたビューやコントローラーはそれぞれ適切なモデルからデータを受け取り、自身の状態を更新する。
MSDNのMVC記事まとめ
MSDNの記事がわかりやすかったのでまとめた
背景
- コンピュータシステムの多くは データ を取ってきて、 ユーザ に表示する
- ユーザがデータを変更したら、 データストア の内容を更新することもある
そのため、データストアとUI間のデータの流れに目がいきがちであり、パフォーマンスのため、またはコード量を減らすために、しばしばその処理を密結合にしてしまうことがある。
このアプローチには2つ問題点がある。
- UIはデータストアと比較して頻繁に変更が入る傾向にある
- 複雑なアプリケーションでは、単にデータストアからデータを送受信するだけでなく、複雑なビジネスロジックが存在する傾向にある
問題
ドメインロジックとUIの変更をそれぞれ手軽に独立して行えるようにするためには、Webアプリケーションをどのようにモジュール化すれば良いのだろうか
前提
問題を解決する際に、次のようなことを考慮する必要がある
- UIロジックはビジネスロジックに比べて頻繁に変更が入る傾向に有る
- 特にWebアプリケーションにおいては顕著
- 例えば、新しい画面が追加されたり、すでに存在するページのレイアウトが変更されたりすることが多い
- したがって、サーバー側に処理をまとめたシステム(シンクライアントシステム)は、アプリケーションを再配布しなくて済む
- プレゼンテーションコードとビジネスロジックがひとつのオブジェクトとして結合している場合、UIだけを変更したくてもビジネスロジックを含んだオブジェクトを変更しなければならない
- これはUIの些細な変更であっても、エラーを誘発したり、再テストが必要だったりすることを意味する。
- 場合によっては、アプリケーションは同じデータを異なる形で表示することがある
- 例えば、アナリストはスプレットシートを好み、マネージャは円グラフを望むことがある
- またそれらを同時に表示することも十分に考えられる
- もしユーザがデータに変更を加えた場合、すべてのビューに反映されるべき
- デザインと複雑なビジネスロジックを作る作業は全く異なるスキルであり、両方のスキルを持ち合わせる人は滅多にいない
- そこで、これらの作業はバラバラに実行できることが望ましい。
- プレゼンテーションはデータソースからデータを取得し、データをフォーマットして表示する
- 表示されたデータに応じてユーザーはなんらかのアクションを行い、そこからうまれた変更を反映させるためにビジネスロジックへ制御が渡る
- WebアプリケーションはHTTPのステートレス性の上で、異なるアクションを実現しなければならない
- UI層のコードは、ビジネスロジックと比較して、デバイスに依存する傾向にある
- したがって、もしプラウザベースのアプリケーションからモバイル端末などに移植したい場合はUI層のコードを置き換える必要がでてくる
- UIに関するコードとビジネスロジックを分離しておくことは、移植を素早く行えるようにしたり、移植後のビジネスロジック層のエラーを減らしたりすることに役立つ
- UI層のテストは難しくて、実行に時間がかかる傾向にある。したがってUI層と直接結びついているコードを減らすことはテスタビリティ向上につながる。
解決策
Model-View-Controllerパターンは、ドメインモデル、プレゼンテーション、ユーザーからの入力に対するアクションの3つのパートに分離をする[Burbeck92]。
- Model: モデルは状態に応じたアプリケーションドメインの振る舞いを扱い、主にControllerからの操作に応じて状態を変化させる。
- View: ビューは表示される情報を扱う
- Controller: マウスやキーボードなどのユーザからの操作を解釈して、モデルやビューに適切に伝える
- ビューとコントローラーはモデルに依存してよい
- モデルはどちらにも依存してはいけない
- これにより、モデル層とプレゼンテーション層のビルドやテストをバラバラに行うことができるようになる
- ビューとコントローラーの分離はリッチクライアントアプリケーションでは二の次となる
- 実際のところ多くのUIフレームワークではひとつのオブジェクトにビューとコントローラーの責務をおいているものが多い
- 一方、Webアプリケーションにおいてはビュー(ブラウザ)とコントローラー(HTTPリクエストをさばくサーバーサイドコンポーネント)は明確に分かれている。
Model-View-Controller はUIロジックとビジネスロジックを分離するための基本的なデザインパターンである。有名であるがゆえに、多くの間違った説明がなされている。
特に Controller は色々な文脈で異なる説明がなされている。ただし、Webアプリケーションにおいてはビューとコントローラーは明確に分かれているために、こういった曖昧さを解消するためには丁度良い。
passive型 MVC
Steve Burbeckは、In Application Programming in Smalltalk-80: How to use Model-View-Controllerの中で passive 型と active 型の2種類の MVC のかたちについて説明している。
passive型MVCはあるコントローラがモデルを排他的に制御するときに用いることができる。コントローラはモデルを変更し、ビューにモデルが変更されたので内容を更新する必要があることを伝える。このやり方だと、モデルはビューやコントローラと完全に独立した形になり、モデルはビューに変更を伝える手段がない状態になる。HTTPプロトコルなどがこのパターンに合致する。ブラウザがサーバーから更新を受け取るための、この上なくシンプルな方法である。ブラウザはユーザからの入力に応じてビューを表示するが、サーバー上のデータの変化を検出することはできない。ユーザが明示的に更新を問い合わせる必要が有る。
active型 MVC
active 型はコントローラが関わらなくてもモデルの状態が変化する際に用いられる。このモデルの変化は他の入力ソースからデータの変更が発生してしまう場合に起こりうる。基本的にはその変更はビューに反映されるべきである。電光掲示板を作るときにはこうしなければならないだろう。表示内容に変更があった場合、データ取ってきてビューに反映させる必要がありそうである。
しかし、MVCパターンを用いる大きな目的のひとつに、モデルとビューを分離するというものがあったことを思い出そう。モデルがビューに変更を通知するとなると、ふつうにやれば依存関係が発生してしまう。そこでGoFのObserverパターンを思い出そう。これはオブジェクト同士の依存を避けつつも、変更を通知することを実現するデザインパターンである。それぞれのビューはObservarインターフェースを実装して、モデルを購読する。モデルが変更されたら、登録されているすべての observer に通知を行うという仕組みになる。このやり方はしばしば、publish-subscribe と呼ばれる。モデルはビューに関する情報は一切必要としない。たとえば、メニューのオプションを使えなくする必要があるなど、コントローラーがモデルの変更通知を必要とする場合、すべてのコントローラはobserverインターフェースを実装する必要がある。また、たくさんのビューがある場合、それぞれ異なる領域ごとにモデルを定義し、各ビューは関心のあるモデルのみを subscribe すると良いでしょう。
もたらされる影響
MVCのもたらす恩恵
- 複数のビューをサポートできる: モデルとビューが分離されているため、モデルからビューへ直接の参照は存在しない。したがって、同時に同じデータを様々なビューで表示することができる。これはWebアプリケーションにおける複数のページは同じモデルオブジェクトを使っていることがあることからもわかるだろう。またユーザがページの見た目を変更できるようなWebアプリケーションの存在からも理解することができるだろ。そういったページは同じモデルから得られた同じデータを、異なる方法で表示することができている。
- 変更に強くなる: UIはビジネスロジックと比較して変更が生じやすい。ユーザは異なる配色やフォントやレイアウトを好むかもしれないし、デバイスに特化したインターフェースを望むかもしれない。モデルがビューに依存していないので、新しいビューのセットをシステムに追加しても、モデルは何の影響も受けない。結果として、変更の影響はビューだけに限られる。この部分をさらに細分化したパターンとして Page Controller と Front Controller というパターンがある。
MVCのもたらす弊害
- 複雑になる: MVCパターンは遠回りなやり方であり、システムを複雑にする。またデバッグの難しいイベント駆動なコードベースになる傾向にある。
- 頻繁な更新に対するコストが大きい: モデルとビューを切り離すことは重要だが、決してモデルがビューの存在を無視してよいということではない。モデルが頻繁に更新を行うような場合、ビューにそれを反映させるコストは大きなものとなるだろう。特にグラフィカルなユーザーインターフェースにおいては顕著になる。このような10教科ではビューは描画に失敗することもあるだろう。したがってモデルを作成するときにはビューを念頭に置くことは重要だ。場合によってはモデルの複数の変更をまとめてビューに通知するなどということも必要なはずである。
Document-View パターン
Model-View-Controllerの責務分離は行ったまま、ViewとControllerを結合 したものを Document-View パターンとよんでいる。今日の、多くのGUIプラットフォームではこのパターンが用いられている。VC++のMFCなどはこのパターンの実例のひとつになる。