MVC による設計

Closure Library による MVC パターン

MVC パターンとは?

MVC パターンとはインターフェースを Model 、 View 、 Controller の 3 つに分割して設計する方法です。

それぞれがどの機能を担当するのかについては様々な流儀があります。

一般的には Model はロジック、 View は表示、 Controller は状態指示というように分担させます。

このような MVC パターンの目的は、表示と入力(状態指示)の互換性を高くすることです。

このパターンは多くのプロダクトで採用され、 MVC パターンを実現する様々なライブラリ・フレームワークがあります(参考:JavaScript MVCフレームワークはすでに十種類以上、その比較や最新情報などのまとめ)。

様々な流儀があるとはいえ、実績のあるパターンといえるでしょう。

たとえば、データベースに在庫状況の時系列変化が入力されていて、これを表示する Web サイトを考えます。

MVC パターンの1つの例として以下のように役割を分担させます。

  • Model = 在庫状況の時系列データを扱うデータベース

  • View = データベースの内容を表示する

  • Controller = ユーザの入力をデータベースに指示

さて、在庫状況の表示方法を考えましょう。

どのような表示方法が考えられるでしょうか。

表形式で表示するのもよし、グラフで表示するのもよし、あるいは音声によって読み上げるというのもあるかもしれません。

この例でいいたいことは、「1つの Model につき、様々な表現方法がある」ということです。

これを実現させるために、表現方法に依らないロジックを Model として分離、表示部分は View に分離します。

また、先の例で在庫データの変更もできるようにすることを考えます。

すると、 Model に変更を通知する必要が出てきますね。

この通知方法はデータの入力方法によって様々なものが考えられます。

例えば、キーボード入力、ボタンやチェックボックスなどのマウス操作による入力、あるいは音声入力などがありますね。

この例でいいたいことは、「1つの Model につき、様々な入力方法がある」ということです。

したがって、 Model への入力(または状態指示)を Controller へ分離させることで、入力方法の互換性を高めることができます。

Closure Library における MVC パターン

Closure Library の MVC パターンを紹介します。

実は Closure Library では純粋な MVC パターンは使われていません。

View と Controller をくっつけて View-Controller とする M(VC) パターンに近い変則的なパターンが使われています。

というのも、UI コンポーネントの表示・入力は同じ DOM 要素によっておこなわれることが多いため、

View と Controller を分割するメリットよりもデメリットの方が大きいのです。

例として、ボタンの MVC パターンを考えてみます。

ふつう、ボタンには button 要素が使われるます。

したがって、ボタンは表示と入力に使う要素が同じです。

すると、通常の MVC パターンの View と Controller は同じ button 要素を別々に持たなければならないことになります。

これは View と Controller のデータ結合度を増加させるため、望ましい設計ではありません。

そこで View と Controller を統合し、リソースを共有させることでデータ結合を View-Controller 内に隠蔽するようにします。

これが M(VC) パターンの設計思想です。

ただ、データ表現が複雑になると View-Controller 内のコード量が大きくなりすぎるという欠点があります。

そのため、Closure Library では DOM 要素の作成・操作を View として切り離すという手法を採っています。

この View は状態を持たないクラスで、View-Controller の指示に従って要素を描画するという働きをもっています。

コンストラクトにかかるオーバーヘッドを軽減するため、View はインスタンスを 1 つしかもたない"シングルトンクラス"にするのが Closure Library の作法です。

粒度の検討

トップダウンアプローチ

考えているインターフェースを UI コンポーネントに分離していき、これ以上分けることができない、というところまで分割します。

これらの部品を最小の UI コンポーネントと呼びます。

例えばお問い合わせフォームにおける最小の UI コンポーネントは、テキスト入力要素・チェックボックス・ボタンなどが考えられるでしょう。

次に、複数の部品をまとめて1つの UI コンポーネントとしてまとめられないかどうかを検討します。

例えばツールバーはボタン・リンクというような最小の UI コンポーネントをまとめた1つ上位の UI コンポーネントと考えられます。

さらに、リッチテキストエディタを考えるとツールバーとテキストエリアの2つの UI コンポーネントから構成されるより上位の UI コンポーネントであることがわかります。

これを繰り返して最終的に1つの大きな UI コンポーネントにします。

ボトムアップアプローチ

MVC の設計は最小の UI コンポーネントからおこない、次に上位の UI コンポーネントというように、徐々に UI コンポーネントの大きさを大きくしながら実施するとうまくいきます。

Model の設計

Model の分離

まず、アプリケーションのなかで表示に関わらないデータ構造・アルゴリズムがあるかどうか探します。

見つかったらそれが Model でほぼ間違いありません。

多くの場合、 Model はデータベース機能をもつことになります。CGI を経由するデータベースへのアクセス方法を抽象化するためにも使われます。

データベース以外の場合では、シミュレータの演算部分やゲームロジックが考えられます。

これらは、表示に依らず法則・ルールが一定であるため Model になります。

Model のセッター/ゲッター定義

さて、 Model をうまく分離できたら Model のセッター/ゲッターを定義します。

単にプロパティにアクセスすればいいじゃん、と思うかもしれませんが Model については set~ と get~ というようなセッター/ゲッターを使って操作することが望ましいです。これはセッター/ゲッターを通さないアクセスはイベントの励起をしにくいからです。

逆にいえば、どう考えてもイベントとつながりようのないメンバのセッター/ゲッターを定義する必要はありません。

Model のイベント定義

Model が発行しなければならないイベントをリストアップします。

このとき、イベントの必要性は Model からのフィードバックがユーザにとって必要かどうかで判断すると良いでしょう。

たとえば、Model がデータベースであれば、データの変更イベントは必須です。

View の更新はデータの変更後直ちになされることをユーザは望んでいるからです(View が Model のイベントを監視していることが前提です)。

エラー発生イベントも Model の状態のフィードバックとして重要ですからほぼ必須と思われます。

このようにして、 Model の発行すべきイベントを列挙していきます。

代表的なイベントの例を以下に示します。

  • timeout

      • 処理がタイムアウトしたときに励起されるイベント

  • ready

      • 処理の準備ができたときに励起されるイベント

  • datachange

      • データの変更されたときに励起されるイベント

  • abort

      • 処理が中止されたときに励起されるイベント

  • error

      • エラーが発生したときに励起されるイベント

  • success

      • 処理が成功したときに励起されるイベント

  • complete

      • 処理が完了したときに励起されるイベント

Model に使えるモジュールの調査

いろいろなところで言及していますが、要求を満たせそうなモジュールの調査には力を割くことが賢明です。

調査の範囲は goog.events.EventTarget のサブクラスと goog.ds.DataManager で十分です。

  1. 求めている機能そのままのモジュールが Closure Library に存在するか

      1. コーディング・デバッグの必要なし

  2. 求めている機能の一部を Closure Library のモジュールによって実現できるか

      1. 継承を考える

goog.ds.DataManager を Model として利用するよい例が Google API Expertが解説する Closure Libraryプログラミングガイド で紹介されています。

参考に読んでみることをお勧めします。

View Controller の設計

Model

View の設計(必要ならば)

Closure Library における特殊な MVC パターン

前の例では M(VC) パターンを紹介しました。

じゃあ、入力と表示が離れている場合はどうなるんだ?と思う方もいるのではないでしょうか。

もちろん、入力と表示が離れている場合は無理に View と Controller を統合する必要はありません。

むしろ、この場合は入力の互換性を向上させるために View と Controller を統合しない方がよいと思われます。

この場合は以下のようにするとよいでしょう。