MVCモデル

インタラクティブなアプリケーションの作り方


MVCモデルという用語は、今時のIT関係のエンジニアの方なら誰でも名前くらいは聞いたことがあるはずです。

大抵の人は「あれだろ?入出力と他のロジック切り離して...云々」みたいな認識でしょう。概ね正解です。しかし、具体的にどう実践しているかというレベルの話になると、仕事で使っている開発環境が対応してるからとかStrutsでWebアプリを開発して...といった感じではないかと思います。

実際、以前僕がいた職場でもこの話をすると、MVCモデルは観念的なもので現実の実装とは別ものという人がいました。また、ある人はフレームワーク等で提供されるものなので個々の技術者が強く意識するものではないと答える人もいました。
...本当にそうでしょうか?

MVCモデルは観念的なものではありません、設計や実装開発体制にまで影響するソフトウェア開発手法です。設計や実装と別物というのは理解できていないことに対する良い訳です。また、常に誰かが提供してくれる訳でもないし、頭のいい誰かが用意して皆がそれを使うだけの小難しい概念でもありません。適切に理解さえすれば規模を問わず誰にでも実践できる開発手法です。

もちろん何もない野っ原の様な開発環境でもMVCモデルに基づいた設計実装は可能です。むしろMVCモデルを使わずにアプリを開発する方が遥かに力技で難しいと言えます。

1. MVCモデルとは
2. もう少し具体的な解説
2.1 MVCの構造に具体的な処理を当てはめてみる
3. MVCモデルに基づいたプログラム作成
3.1 アプリケーションの仕様
3.2 ビューからの入力通知の洗い出し
3.3 ビューの通知を受け取った時のコントローラの挙動
3.4 モデルからの通知の洗い出し
3.5 モデルの通知を受け取った時のコントローラの挙動
4. MVCモデルを使うメリット
5. MVCモデルの限界
6. WebアプリケーションとMVCモデル2




1. MVCモデルとは

MVCモデルはゼロックスのパロアルト研究所で考案されたインタラクティブなアプリケーションを作るためのプログラミングモデルです。

MVCモデルではアプリケーションの機能をUI(ビュー)とその他ロジカルな処理(モデル)、それらを制御する部分(コントローラ)の3つモジュールに分離することで設計や実装を容易にします。

ビュー、モデル、コントローラは以下の様な構成されます。パフォーマンスやプラットフォームの制約を無視すれば、どのような「インタラクティブなアプリケーション」もこの形式で表現できます。



図1:MVCモデルのモジュール構成



この図を見て他のサイトや資料で書かれている構成と違うと感じる方が少なからずいると思われますが、それはWebアプリケーションに特化した「MVCモデル2」という派生形です。ここでは本来のMVCモデルについて解説します。MVCモデル2については後述を参照してください。

MVCモデルの目的はユーザに対する表示や入力(以下、UI)を扱う機能とその他の機能を分離して設計実装することです。説明するまでもないとは思いますが、アプリを作成する際にUIとその他の機能を分離すると多くのメリットがあるからです。

UIに関連する機能はビューに、その他はモデルが管理します。コントローラはビューとモデルの仲立ちをするアプリの最上位モジュールです。
なお、ここでいうモデルと「MVCモデル」の「モデル」と言う用語は別の物です。「MVCモデル」の「モデル」は「プログラミングモデル」の「モデル」です。本サイトでは特に説明がない限り「モデル」という用語はこの図に書いてあるビューと対を為すコントローラの下位モジュールの意味で使います。

では各モジュールの役割をもう少し詳しく説明しましょう。

(1)ビュー
ユーザに対する表示、入力(以下、UI)等の機能は全てビューに実装します。
入力はキー押下やマウスのクリックはもちろん、メニュー選択やその他スピナやテキスト入力フィールドの入力値の変化といった抽象的なものも全てビューが受け取ってコントローラへ伝えます。

余談ですが...「ユーザーに対して」という意味では音声も含まれて当然ですが、マルチメディア系の機能は一般に視覚的な表示場所だけをビューで確保して、あとはモデル側で扱うことが多い様に感じます。

ビューはアプリの見栄えを受け持つアプリの「顔」です。言い換えればビューだけでそのアプリの表示操作とユーザ入力の取得の全てを実現できなければ行けません。
(2)モデル
ビューに属する機能以外の全てを、モデルに実装します。大雑把な分け方ですが概ねこの判断基準で通用します。
理由の一つはアプリの見栄えを変更しただけでコード全体の修正が及ぶのを避けるためです。
同時に表示に関わりのない処理を差し替えた時に表示周りの変更を避けるためでもあります。

またプラットフォーム依存の大きいAPIの大部分がUI周りに集中しているため、UIを分離するとプラットフォーム間の移植性が確保しやすいという理由もあります。

また、ビューが入力をコントローラへ伝える様に、モデルも内部状態の変化や外部からの通信によって何らかの動作を起こしたい等の場合はその旨をコントローラへ伝えます。
(3)コントローラ
コントローラはビューとモデル間の相互参照を避けるための仲介役です。ビューとモデルの両方を知っているMVC構造を持つアプリの最上位モジュールです。

コントローラはビューやモデルからの通知を受け取ります。ここでいう通知とはユーザの入力や内部状態の変化、子スレッドからのデータ受信や処理の終了等、コントローラへ伝えるたい発生タイミングを予測できない事象全てを指します。

通知を受け取ったコントローラはその通知に応じて、必要な操作をビューやモデルに指示します。

コントローラは通知によってのみ動作するモジュールなので、原則アプリとしての機能を実装してはいけません...理由はわかりますよね?
アプリ固有の機能である表示やデータの処理はすべてビューまたはモデルに属するからです。コントローラが持つ機能は通知の処理(以下、通知ハンドラ)の中から、ビューやモデルに対してそれらが持つ機能の実行を指示するだけです。また、全ての動作の起点は通知ハンドラです。初期化時を除いてコントローラ自ら動作を起こすことはありません。

MVCモデルではモデルとビューの上位モジュールとしてコントローラを導入します。ビューとモデルだけでは相互に参照し合うため完全に分離できないからです。そのためビューとモデルを完全に切り離すために仲立ちとなるコントローラが必要になります。

詳細は別紙
コントローラはなぜ必要か? の解説を参照してください。



2. もう少し具体的な解説

MVCモデル自体はプログラミングモデルなので特定の実装やプラットフォームには依存しません。言い換えれば「開発者の都合に合わせて好きに実装しろ」という事です。
実際には既存のフレームワーク多くが(不完全であっても)MVCモデルを取り入れているので、あまり意識することはないかもしれません。
また、MVCモデルは一般にはオブジェクト指向が前提であるかのの様にも思われていますが、オブジェクト指向言語を使わなくとも(一応)実現可能です。

...とはいえ、自由度が大きい反面、抽象的すぎるためアプリケーション開発でどのように反映すれば良いのか想像しづらいとも言えます。

ここでは具体的なアプリの各機能や動作をどのようにMVCモデルで実現するかを解説します。

2.1 MVCの構造に具体的な処理を当てはめてみる

現実のアプリで実装される機能を適当に挙げて書き込んだものが以下の図です。
とりあえず、ここではMVCの各モジュールはオブジェクト(註)であると考えてください。また、下向きの↓操作はメソッドの呼び出し。上向きの↑通知はJavaのユーザ定義イベントとします。(註:一番シンプルかつ有用な形ですが、MVCモデルは実装に言及していないので必ずしも単一のオブジェクトであるとは限りません。さらに言えば、オブジェクトであるとも限りません。)



図3:各モジュールが実装すべき処理の例



各モジュールの実装すべき機能を説明します。

(1)ビュー
ビューはユーザに対する入出力を制御します。
故に、UI(ウインドウやダイアログ)もこのモジュールが保持します。表示/入力に関わる機能はすべてここに組み込まれます。

以下、このモジュールに実装するメソッドの例です。

a. UIの表示制御
・ウィンドウの表示制御
・ウィンドウの位置制御
・ウィンドウのタイトルの設定
b. 入力項目の設定
・入力項目の初期化
・ボタンのラベル設定
・ラジオボタンやチェックボックスの選択制御
・入力モードの操作
c. データの表示
・ファイルから読み込んだり通信した内容の表示
・プログラムによる処理結果
・その他ユーザに知らせたい情報の表示
d. エラーメッセージの表示
・ユーザの誤操作等の警告や注意
・処理中に発生したエラーの通知

また、キーボードやマウスから受け取った物理的な入力を解釈してアプリ固有の「操作要求」としてコントローラに通知するのもビューの役割です。

(2)モデル
モデルはユーザに対する入出力以外の機能を制御します。ファイルアクセスや通信、データの計算や加工その他の機能を持ちます。

アプリケーションが扱うデータの原本はモデルが管理します。表示する場合もモデルが保持するデータのコピーをビューに渡します。入力値を除いて、一度ビューに渡したデータを読み戻さないでください。理由はビューに渡すデータは表示のために加工した時点で劣化するうえに、読み戻す際に逆の加工も必要になるからです。

以下、このモジュールに実装するメソッドの例です。

a. ファイルアクセス
・初期設定の読み書き
・データ(ドキュメント)の読み書き
b. 通信
・データ送受信
c. その他処理
・データの加工や計算
・データ(原本)の維持管理

また、以下の通知もモデルの役割です。
・スレッドによる処理の終了やタイマによる時間経過の通知
・通信や機器からの接続要求やデータの受信
・内部状態の変化
・その他非同期的に発生した事象

ただし、スレッドやタイマ、通信を行わない簡単なアプリケーションの大部分はモデルからコントローラへの通知は不要です。

(3)コントローラ
ビューやモデルから送られる通知に対するハンドラを実装します。受け取る通知の種類はアプリケーション毎に異なります。
コントローラの役割は通知の従って、ビューやモデルが持つメソッドの実行を指示する事です。従ってビューとモデルの間でやり取りされるデータの形式変換を除き、独自の処理を持つべきではありません。



3. MVCモデルに基づいたプログラム作成

実際にMVCモデルに基づいてアプリケーションを作成してみましょう。ここではラーメンタイマーを例にして解説を進めてゆきます。

なぜラーメンタイマーか?その理由はMVCモデルを使わずに作ってみればわかります。MVCモデルとコールバックを使わなければビューとモデルを上手く分離できない典型的なケースだからです。納得できない人は簡単なアプリなので実際に作ってみると良いでしょう。もちろん場当たり的な実装で「頑張って」小細工を弄すればなんとか分離できます。(←当然小細工の結果以後の修正や動作の把握は難しくなります)とはいえ、場当たり的な「頑張り」を要求している時点でそれはもはや「技術」ではありません。なによりすでにMVCモデルという単純で一般化された手法があるのにそんな労力をかけるのは無意味です。

3.1 アプリケーションの仕様

今回の例にするラーメンタイマーは以下の仕様を持ちます。機能的には別紙「コントローラはなぜ必要か?」で登場した3分タイマーとほぼ同じですがさらにリセット機能を追加しています。
・初期状態はスタートボタンはイネーブル、リセットボタンはディセーブル
・スタートボタンで時間の計測を開始
・スタートから3分経過するとビープ音を鳴らす
・スタート後に1秒おきに残り時間表示を更新する
・リセットボタンで計測を中断して時間をクリア
・スタートボタンは計測中はディセーブル、リセットボタンは計測中以外はディセーブル



アプリケーションの画面例



先に結論を示しますが、前述の仕様から抽出した各モジュールに実装すべき機能は以下の様になります。



図4:ラーメンタイマの各モジュールに実装する機能の例



では、順を追って機能の抽出してみましょう。

3.2 ビューからの入力通知の洗い出し

インタラクティブなアプリケーションの動作は一般にキー入力やメニュー選択等ユーザ操作が起点になることが多いので、入力の洗い出しを始めに行います。
このアプリケーションの入力は以下の2つです。
・スタートボタン押下
・リセットボタン押下
これらはユーザ入力に依るものなので、アクションイベントを受け取ったビューがコントローラへ発行する通知に置き換えます。

従って、ビューがサポートすべき要求は、
・スタート要求通知
・リセット要求通知
となります。



図5:ビューの通知



ここで注意すべき点は 入力イベント≠ビューの通知 であることです。
このアプリケーションではスタートボタンを押下すると「スタート通知」、リセットを押下すると「リセット要求」が発生しますが、入力イベントとビューの通知は必ずしも1対1とは限りません。ビューがコントローラへ通知を発行するのはアプリケーションとして何らかの行動を起こす必要があるからです。この場合の行動とはデータの加工や保存あるいはファイルアクセス等といった表示/入力以外の動作のことです。
言い換えれば、ユーザが何らかの操作を行ってもビューの内部で処理が完結する場合は通知は不要です。

また、ビューが発行する通知名は、特に必要が無い限り(ボタン、メニュー等)物理的なUIに依存させない様にしてください。コントロールがUIの仕様から完全に独立していれば、UIに変更が発生してもしても修正はビューの中だけで完結します。例えば、将来メニューその他の方法によりタイマのスタート方法が追加されることを考えてみてください。ビューからの通知が「スタートボタン押下通知」ならばビューの通知名を変更するか「スタートメニュー選択通知」を追加しなければなりません。いずれにせよ通知を受けるコントローラにも変更が及びます。



図6:ビューの通知名が物理的な入力に依存すると...




もう一つ注意いて欲しい点は、ビューの通知名称が「スタート要求通知」と「リセット要求通知」であることです。ユーザのボタン押下の通知が「要求」である理由は、ビューの役目はあくまで入力イベントから「タイマをスタート(またはリセット)しろ!」というユーザの意思を判断してコントローラに伝えることだからです。ビューは直接タイマスタート(またはリセット)操作に関与しません。さらに、後述のモデルに実装するタイマスタート(またはリセット)の処理名との重複を回避するためでもあります。

3.3 ビューの通知を受け取った時のコントローラの挙動

ビューから通知を受け取った時のコントローラの挙動を決定します。

(1)スタートボタン押下
スタートボタンが押下されるとコントローラは以下の操作を行うことにします。

a. モデル:タイマ動作を開始(注:スタート後の動作はモデルでスレッドを生成して時間を1秒毎にカウント)
b. ビュー:ボタン状態を計測中に変更(注:スタートボタン=ディセーブル、リセットボタン=イネーブル)
c. モデル:残り時間を参照
d. ビュー:モデルから参照した残り時間を表示

ただし、cとdは後述の初回カウントダウン時の表示更新で行っても良いので省略します。



図7:スタート通知発行時の動作シーケンス



(2)リセットボタン押下
スタートボタンが押下されるとコントローラは以下の操作を行うことにします。
a. モデル:タイマ動作をリセット
b. ビュー:ボタン状態を初期に変更(スタートボタン=イネーブル、リセットボタン=ディセーブル)
c. ビュー:残り時間(00:00)を表示
ただし、cも後述のリセット完了時の通知処理で行っても良いので省略します。



図8:リセット通知発行時の動作シーケンス



3.4 モデルからの通知の洗い出し

モデルがコントローラに伝える通知を洗い出します。以下の動作を実現するには時間の計測中にモデルからコントロールに、1秒毎のカウントダウンとタイムアップを通知する必要があります。
・スタートから3分経過するとビープ音を鳴らす
・スタート後に1秒おきに残り時間表示を更新する
・リセット完了

従って、以下の3つの通知を実装します。

(1)カウントダウン(計測中1秒毎に発行)
(2)タイムアップ(計測中1秒毎に発行)
(3)リセット完了

なお、今回は「リセット完了」は必須ではありませんが、スレッドを強制終了出来ない場合に終了や中断完了通知が必要になるので参考のため実装しておきます。

3.5 モデルの通知を受け取った時のコントローラの挙動

モデルから通知を受け取った時のコントローラの挙動を決定します。

(1)カウントダウン
カウントダウン時には以下の操作を行うことにします。
a. モデル:残り時間を参照
b. ビュー:モデルから参照した残り時間を表示



図9:カウントダウン通知発行時の動作シーケンス



(2)タイムアップ
カウントダウン時には以下の操作を行うことにします。
a. ビュー:残り時間(00:00)を表示
b. ビュー:ビープを鳴らす



図10:タイムアップ通知発行時の動作シーケンス



(3)リセット完了
リセット完了時には以下の操作を行うことにします。
a. ビュー:残り時間(00:00)を表示



図11:タイムアップ通知発行時の動作シーケンス



以上、洗い出した機能を一般化してまとめたものが前述の図3になります。
この仕様で実際に作成したコードとドキュメントは別紙
ラーメンタイマの解説 を参照してください。



4. MVCモデルを使うメリット

MVCモデルを使うことで享受できるメリットを以下に挙げます。

(1)仕様変更がアプリ全体に波及しづらい
ビューが保持するUIを変更してもアプリの機能そのものが変わらない限りモデルを修正する必要はありません。同様にもモデル内部の実装を差し替えてもUIが同じであればビューに修正は不要です。

(2)移植が容易
移植の際に最も影響を受けるのはプラットフォーム依存の大きいUI周りのAPIです。APIの名称やデータ型だけではなく制御構造さえ変わる場合が多々あります。
故に、UIの実装をビューに限定するということは、移植の際にビューを差し替えるだけで作業の大半が完了することを意味します。
ビューがモデルと切り離されていることで他のプラットフォーム(OSやCPU、言語等の動作環境)への移植が容易になります。

(3)開発作業の定型化
新規開発であればほぼ全てのインタラクティブなアプリケーションが同じスタイルかつ同じ手順で開発できます。

(4)相互参照の回避が容易
MVCモデルでは階層を持つモジュール間の相互参照が常にビュー − コントローラ、モデル − コントローラの間に現れます。また、理由も明確に分っているため定型的な回避が容易です。
コントローラへの参照はほぼ100%ビューからの場合「入力通知」、モデルからの場合「通信やスレッド処理の終了、内部状態の変化等」の通知です。従ってこれらの参照をコールバックで実装する事により相互参照を解消できます。
なお、相互参照の問題については別途用意した解説を参照してください。


(5)作業の見積もりが正確になる
各モジュール間のインタフェース(通知、メソッド)を決定した時点で、開発に必要な作業項目数が特定されます。この結果より具体的な作業見積もりが可能です。
勿論、MVCモデルを利用しなくても力技で開発する事は可能ですが、「どのくらいかかりますか?」と聞かれても大抵は「実際に作ってみないとわからない」という答えが返るでしょう。 明確な手法を持たずに力技で作ると作業量が不明確なうえにどこでつまずくかも予想できないからです。

(6)ViewとModelの平行開発が可能
モデルとビューのインタフェースを決定してそれぞれにダミーのコントローラを用意すれば、個別に設計から単体テストまで進めることが可能です。作業が平行して進められる保証があるという事は、即ちスケジュール遅延を人員の補充で手当できることを意味します。

(7)ドキュメンテーションが容易
前述の図3の様な各モジュールの機能を書き込んだMVCモデルの構成図はソースコードと完全に対応させる事ができるため、あらかじめドキュメントを書く場合だけではなく、作成後にドキュメント化することも容易です。また、UMLのシーケンス図やシナリオに展開することも容易です。



5. MVCモデルの限界

MVCモデルは単一ドキュメントのアプリケーションを記述する場合、非常に明瞭で一貫したな構造を実現できます。しかし、単純なMVCモデルでは以下に挙げた要求を満たすことは困難です。

(1)マルチドキュメント化
アプリケーションをマルチドキュメント対応にする場合、モデル − ビュー間でウィンドウ等のUIとドキュメント(ファイル)の対応付けが必要になります。

実はドキュメントに限らず、MVC構造を持つ層が複数重なった時に設計や実装が複雑になる事がこの問題の本質的です。このためMVC構造を持つサブモジュールを動的に組み込む場合別な工夫が必要になります。(=これは、MVCモデルに限った話ではありませんが...)

(2)セーブ機能をモデルに実装すると表示状態の保存が難しい
例えばワープロでウィンドウサイズやスクロール位置も保存したい場合、モデルは表示の実装を知らなければ情報をセーブできません。少なくとも、モデルは特定の形式のウィンドウやスクロール表示を前提として情報をセーブしなければなりません。つまりこれはモデルとビューを分離したにもかかわらず、モデルがビューの仕様に依存してしまう事を意味します。たとえば、ビューの表示方法が変更されれば本来ビューから独立しているはずのモデルまで修正が必要になります。これではMVCモデルを採用した意味が薄れてしまします。

6. WebアプリケーションとMVCモデル2

不毛なので詳しくは解説はしません。理由は他の多くのサイトで解説されていることと、MVCモデル2は美しいとはいえないからです。詳細を知りたい方は「MVCモデル2」で検索してみてください。

MVCモデル2は主にJSPやサーブレットを使用するWebアプリケーションで広く採用されている構成です。ビューとモデルとコントローラに分けるところまでは同じですが、入力はビューではなくコントローラが受け取ります。理由はユーザの入力内容はリクエスト経由でサーバに伝えられるためです。

最近はIT系の現場ではMVCモデルと言う場合、MVCモデル2を指すことが多いのですが、MVCモデル2はWebの都合に合わせて変形した(=ねじ曲げた?)バリエーションに過ぎません。



図12:MVCモデル2



なお個人的には賛同しかねますが、世間一般ではコントローラ=サーブレット、ビュー=JSP、モデル=DBという解釈がされています。


トップに戻る