.NET開発者中心 厳選ブログ記事 開発者が知っておくべき、6つのUIアーキテクチャ・パターン 2011/12/15 |
「.NET開発者中心 厳選ブログ記事」シリーズでは、世界中にある膨大なブログ・コンテンツの中から、特にInsider.NET/.NET開発者中心の読者に有用だと考えられるブログ記事を編集部が発掘・厳選し、そのブログ記事を執筆したブロガーの許可の下、その全文を転載・翻訳しています。この活動により、.NET開発者のブログ文化の価値と質を高め、より一層の盛り上げに貢献することを目指しています。 |
本稿は、記事「matarillo.com: UIパターン」に簡単な校正・加筆を行ったうえで転載したものです。 |
Martin Fowler氏の『GUI Architectures』を訳して公開しようと思ったのだが、FAQページに「PofEAAの続編などは商業出版する予定なので翻訳はしないでほしい」と書いてある。なので翻訳の公開はやめて、「自分の理解を書く」というスタイルにしようと思う。
Fowler氏が説明しているのは、「フォームとコントロール」「モデル・ビュー・コントローラ(MVC)」「プレゼンテーション・モデル」「モデル・ビュー・プレゼンタ(MVP)」の4つ。なお、後ろの2つは、どちらもMVCの変種だ。
氏のサンプルでは、「大気中のアイスクリーム濃度」というネタになっているが、ここでは「BMIによる肥満度判断」にする。実際のところ、動作はほとんど同じだ。
サンプル「BMIによる肥満度判断」 |
■目次
- フォームとコントロール
- モデル・ビュー・コントローラ(MVC)
- プレゼンテーション・モデル
- プレゼンテーション・モデルの2つのバリエーション:アプリケーション・モデルとMVVM
- モデル・ビュー・プレゼンタ(MVP):監視コントローラとパッシブ・ビュー
■フォームとコントロール
よくあるGUIのスタイルというか、VB(Visual Basic)に代表されるポトペタ。「フォーム」は画面そのもののことで、「コントロール」はテキストボックスやボタンといった「ウィジェット」を指している。(ドラッグ&ドロップにより)フォームにコントロールをペタペタ貼り付けてレイアウトし、コントロールのイベント・ハンドラはフォームに置く。
もしデータ・バインド機構があれば、DB(データベース)から取り出してきたデータとフォームに配置したコントロールを、ビジュアル・デザイナで対応付けられて便利。ものによってはDBへの書き戻しなどもコーディングなしに設定できたりする。ただし、データの単純なCRUD(Create/Read/Update/Delete)にとどまらない場合は、やっぱりコーディングが必要になる。
フォームとコントロール |
処理(=データの読み書きやコントロールの読み書きなど)をイベント・ハンドラにシーケンシャルに書き下すことになるので、何をやっているのかが分かりやすくもあるが、読み書きするデータやコントロールが増えてくると同期を取るのが大変面倒だ。UI(ユーザー・インターフェイス)におけるトランザクション・スクリプト(Transaction Script)である。そんな面倒が発生したときは、複数のコントロールを複合させたカスタム・コントロールみたいなものを作ることになるだろう。
また、イベント・ハンドラがコントロールにどっぷりと依存することになる。その結果、UIなしの自動テストが難しくなる。
■モデル・ビュー・コントローラ(MVC)
MVCにはあまりにも多くの派生パターンがあるので、「あなたが言っているMVCは、わたしのMVCと違うね」みたいなことが頻発する。いっそのこと、MVCはNGワードにした方がいいのではなかろうか、とはわたしも思う。なお、Fowler氏はあくまで「古典的な」MVCの説明にとどめようとしている。
まず「モデル」。モデルは単なるデータではなく、ドメイン・オブジェクトであるべきだ。セッタ&ゲッタがあるだけの貧血ドメイン・モデルではない。抽象度の高いメッセージを受け取るというか、呼び出しの粒度が細かすぎないということである。
次は「ビュー」。ビューは表示(出力)だけを責務とするもの。原則として他者から参照されないし、変更もされない。ビューはモデルを監視していて、モデルが更新されたら表示データを取ってきて自分自身を更新する。デザイン・パターンで言うところの「オブザーバ」。なので、1つのモデルが複数の異なるビューに対応付けられることもあり得る。
MVCにおけるビューとモデル |
そして「コントローラ」。わたしの理解では、「コントローラの責務は、入力を受け取って適切なメッセージに変換してモデルに送信すること」だ。理想的にはモデルに対するプロキシであるべきで、1つのモデルのセッタ&ゲッタをガシャガシャ操作するのはおかしいと思う。モデルの内部状態を管理するのはドメイン・ロジックなのだから、モデルの責務だ。仮にコントローラがモデルをちょこまか操作するのを許したとしても、ビューを操作してはいけない。これを許しているのはもはやMVCではない。
なお、ビューと同じく、1つのモデルに複数の異なるコントローラが対応付けられることもあり得る。例えば、PCの内部時計を設定するインターフェイスはCUIでもGUIでもよい、といった場合だ。
MVCにおけるコントローラとモデル |
ということで、複数のビューがオブザーバ・パターンで同時に更新されること(Fowler氏いわく「オブザーバ同期」)と、コントローラがビューを絶対触らないこと(Fowler氏いわく「分離プレゼンテーション」)が古典的MVCの肝となる。
Fowler氏の説明では、GUIにおけるウィジェットはビューとコントローラがペアになっているらしい。例えばテキストボックスがそうだ。だから1画面には、画面ビューと画面コントローラのペアが1つだけあるわけではなく、(テキストボックスのような)小さなペアも複数存在するのが普通のようだ。だからこその「オブザーバ同期」なのだろう。
MVCとウィジェット |
ただし、古典的MVCにも問題がある。それは、「ドメインに属さない状態やプレゼンテーション・ロジックをどこに置けばいいか」という問題だ。例を2つ挙げる。
1つは冒頭に示したサンプル・アプリの例で、BMIに応じてテキストボックスの背景に色を付ける(やせ=白、標準=黄色、肥満=オレンジ、高度肥満=赤)というロジックがその問題に該当する。「フォームとコントロール」であれば全部イベント・ハンドラに書けばよかったので問題にならないが、MVCでは「どこに置くべきか?」を考える必要があり、結局のところ、モデルに置くかビューに置くかのどちらかになる。モデルに置く場合は、ドメインが美しくないのを我慢して、テキストボックスの背景色をモデルに持たせるという方法もあるし、ビューに置く場合は、普通のテキストボックスを継承して、BMIにしたがって背景色が変わるテキストボックスを作るという方法もある。
もう1つの例は、ドロップダウンリストの選択状態によって別のコントロールが使用可能になったり使用不可になったりする場合だ。これもモデルに置いていいものかが悩ましい。
このような問題を解決するために使われているのが、「プレゼンテーション・モデル」や「モデル・ビュー・プレゼンタ(MVP)」ということらしい。
■プレゼンテーション・モデル
これは、割とシンプルな解決法だ。ドメイン・ロジックだけを含むモデルをラップし、プレゼンテーション・ロジックを含むモデルとして「プレゼンテーション・モデル」を用意するというもの。
プレゼンテーション・モデル |
ドメインに関係する属性や振る舞いは通常のモデルに委譲するが、プレゼンテーションに関係する属性や振る舞いは自前で持つようにする。例えばBMIの例であれば、テキストボックスの背景色やドロップダウンリストの選択状態などをプレゼンテーション・モデルの属性として持たせる。そして、ビューはそのプレゼンテーション・モデルを参照するようにすればよい。
古典的MVCの問題はこれで解決できるが、ほかの点が問題になることもあるかもしれない。
1つは、委譲させるためのコードが面倒な場合があるということ。コードをたくさん書かなくても委譲できるならあまり問題にならないだろう。
もう1つはテスト対象コードの量。モデル自体はテストしやすいが、実際に動作させるときはコントローラやビューも必要なわけで、ビューとコントローラをモデルにつなぐ部分や、「オブザーバ同期」部分をテストさせることはやはり難しい。(古典的MVCのところで説明したとおり)ビューとコントローラのペアは1画面に複数あるのが普通なので、テスト対象コードの割合はその分、減る。
■プレゼンテーション・モデルの2つのバリエーション
ここで、プレゼンテーション・モデルのバリエーションを2つ紹介しておこう。アプリケーション・モデルとモデル・ビュー・ビューモデル(MVVM)だ。
●アプリケーション・モデル
これは、Fowler氏がVisualWorks Smalltalkのやり方を念頭に置いて説明しているパターンだ。残念なことに、わたしはSmalltalkに詳しくないので、わたしの理解が正しいかを確かめるすべがない(助けてsumimさん!!*1)。間違っていたらごめんなさい。
*1 追記: sumimさんが「VisualWorks Smalltalkの“アプリケーション・モデル”でBMI checker」(その1、その2)というエントリを上げてくださった。さらに、どちらにもumejavaさんがコメントを残している。お二方のおかげでわたしもVisualWorksについて少し見通しがよくなりました。ありがとうございました! VisualWorksは非商用なら無料でダウンロードできるので、今度遊んでみようかな。 |
アプリケーション・モデルではビューとコントローラを分けて考えないらしい。元記事でも「ウィジェット」と呼んでいるのでそう書く(これは「フォームとコントロール」で「コントロール」と呼んでいたものに相当する)。
アプリケーション・モデル |
基本的なアイデアはプレゼンテーション・モデルに近い。プレゼンテーション関連のロジックは「具象アプリケーション・モデル」に置く。プレゼンテーション・モデルと違うのは、「アプリケーション・モデル」と「アスペクト・アダプタ」という2つのクラスが存在していること。この2つのクラスはライブラリに含まれている。では、アプリケーション・モデルとアスペクト・アダプタは何のためにあるのか? これは、「MVC版データ・バインド」とでも呼ぶべき仕組みの実現のためだ。
まずアプリケーション・モデルだが、ウィジェットと具象アプリケーション・モデルのバインドが簡単にできる。具体的には、「ウィジェットの値が更新されるときにアプリケーション・モデルのプロパティも更新され、アプリケーション・モデルのプロパティが更新されるときは(オブザーバ・パターンで)監視しているウィジェットに通知が行く」という動作をするのだが、その対応付けは具象アプリケーション・モデルのプロパティ名だけで指定できる。
次にアスペクト・アダプタだが、具象アプリケーション・モデルとモデルのバインドが簡単にできる。具体的には、「具象アプリケーション・モデルのプロパティをアスペクト・アダプタにしておくと、アスペクト・アダプタが持つ値の更新はモデルのプロパティに伝播(でんぱ)し、モデルのプロパティが更新されるときは(オブザーバ・パターンで)監視しているアスペクト・アダプタに通知がいく」という動作をするのだが、この対応付けも簡単に指定できる。
というわけで、MVCだと面倒な、オブザーバ同期を構成する部分のコードをあまり書かなくてもよくなる。面倒なところはアプリケーション・モデルとアスペクト・アダプタがやってくれるからである。かなりうまく作られたフレームワークだと言える。
ただし、このパターンの問題点もやはりデータ・バインド部分にある。要するに単純なバインドは簡潔に書けるが、複雑な場合はそうではないということだ。
例えば、VisualWorksのテキストボックスは、背景色をアプリケーション・モデルにバインドすることができない。そうするためにはテキストボックスを継承してカスタマイズしないといけない。また、ドロップダウンリストの現在選択行を保持する場合は、モデルを参照しつつも現在選択行だけは内部に保持するようなアスペクト・アダプタを自分で書かないといけない。ということで、複雑な場合はクラスが増えてしまう。
それが嫌なら、例えばテキストボックスの背景色についてはアプリケーション・モデルから設定するといったように、MVC原則を破るようなコードを許さないといけない。
これはFowler氏の説明では取り上げられていないが、最近のマイクロソフト技術コミュニティで共有されているUIパターンである。
MVVMにおける「ビュー」は、外観およびユーザーとの対話を担当する、ある程度独立性の高いまとまりである。そして、ユーザーとの対話に関係するデータは「ビューモデル」が保持する。ビューとビューモデルのやりとりには、XAMLベースのGUIフレームワークに備わっている双方向データ・バインドなどの機構を使う。そうすることで、ビューモデルがビューに依存しないようにしながら、プレゼンテーション・ロジックをビューモデル側に実装していくことができる。詳しくは、『MVVMパターンの常識 ― 「M」「V」「VM」の役割とは?』を参照してほしい。
MVVM |
■モデル・ビュー・プレゼンタ(MVP):監視コントローラとパッシブ・ビュー
Fowler氏によるアプリケーション・モデルの説明でも取り上げられていたが、オブザーバ同期だけで実現するのが面倒なプレゼンテーション・ロジックでは、やはりウィジェットを直接操作したい*2。それを押し進めたのが、「モデル・ビュー・プレゼンタ(MVP)」である。
*2 sumimさんの2個目のエントリでもそうしていた。 |
MVPを端的に表すなら、“ルーズなMVC”あるいは“「MVC」と「フォームとコントロール」の中間”といったところだろう。「モデル」「ビュー」「プレゼンタ」の特徴は以下のようになる。
○モデル
MVCのモデルと同様に、純粋なドメイン・ロジックだけを持つ。モデル自身はビューやプレゼンタに依存しない。○ビュー
画面の表示とユーザー入力の受付口を両方担当する。MVPのビューはMVCのビューとコントローラ(の一部)が合わさった役目を持っているが、ユーザー入力はプレゼンタに渡す。また、MVPのビューは複数のウィジェットを複合してある程度のまとまりにしたものである。MVPのビューもMVCのビューのようにオブザーバ・パターンでモデルを監視してもよいが、それは必須ではない。○プレゼンタ
MVCのコントローラと同様、ユーザー入力をモデルに伝える役割を持つ。そのうえで、オブザーバ同期で実現できないようなプレゼンテーション・ロジックも持つ。言い換えると、ユーザー入力をモデルに伝えた後、ビューを直接更新してもよい。
ビューを更新するときは、ビューの実装に直接アクセスするのではなく、インターフェイスを経由する。こうすることで、プレゼンタがビューの実装に依存しなくなる。よって、プレゼンタのテストも容易になる(テスト時にビューを差し替えられるため)。
MVPの特徴は以上のとおりだが、ビューがモデルを監視するものを「監視コントローラ」、ビューがモデルを監視しないものを「パッシブ・ビュー」と呼んで区別することもある。
監視コントローラは次のような構造になる。
MVP(監視コントローラ) |
パッシブ・ビューは次のような構造になる。モデルの状態をビューに反映させるのはプレゼンタの責務となる。
MVP(パッシブ・ビュー) |
■
さて、ここまでに説明したUIアーキテクチャ・パターンの違いが分かるだろうか? 違いがより明確になるように、比較表を作ってみた。
ドメイン・ロジックの場所 | プレゼンテーション・ロジックの場所 | オブザーバ同期 | フロー同期 | |
フォームとコントロール | (明記されていない) | フォーム | (なし) | フォームがコントロールを操作 |
MVC | モデル | ビューまたはモデル | ビューがモデルを監視 | (なし) |
プレゼンテーション・モデル | モデル | プレゼンテーション・モデル | ビューがプレゼンテーション・モデルを監視 | (なし) |
アプリケーション・モデル | モデル | アプリケーション・モデル | ビューがアプリケーション・モデルを監視 | アプリケーション・モデルがビューを操作 |
MVVM | モデル | ビューモデル | ビューがビューモデルを監視(双方向データ・バインド) | (なし) |
MVP(監視コントローラ) | モデル | プレゼンタ | ビューがモデルを監視 | プレゼンタがビューを操作 |
MVP(パッシブ・ビュー) | モデル | プレゼンタ | (なし) | プレゼンタがビューを操作 |
UIアーキテクチャ・パターンの違い | ||||
オブザーバ同期=状態の変化がオブザーバ・パターンでビューに反映されること。 フロー同期=ユーザー入力を受けとったときに、ビューを操作して状態の変化を反映させること。 |
■
ということで、いろんなUIアーキテクチャ・パターンの特徴を紹介した。MVCの変種にはいろいろあり、それぞれ考え方が違うことが分かってもらえたかと思う。
では、「実際にどれを選ぶのか」だが、それはなかなか難しい問題だ。「実装のしやすさで選ぶ」というのも1つの基準だ。その場合は、(ライブラリやフレームワークも含む)実行環境次第で適したパターンが変わるだろう。
ほかの基準としてはテストのしやすさが挙げられる。ここで注意しなければならないのは、オブザーバ同期がフロー同期よりテストしやすいというわけではないということ。オブザーバ同期はビューなしでドメイン・ロジックを実行することを可能にするので、ドメイン・ロジック部分のテストにはよいが、ビューとモデルの対応付け部分や、ビューが更新される部分のテストには向いていない。
ドメイン・ロジックはさほど複雑ではないが、プレゼンテーション・ロジックが複雑だという場合であれば、パッシブ・ビューという方法もある。つまり、オブザーバ同期を無くし、プレゼンタが完全にビューを操作するという方法である。こうすれば、プレゼンタのテストでプレゼンテーション・ロジックのほとんどをテストできるということになる。もちろんプレゼンタを動かすにはビューの実装を必要とするが、そこはテスト・ダブル(=テスト用の代役オブジェクト)を用意すればいいだろう。
【筆者プロフィール】 業務では、主にマイクロソフト製品を使用したアプリケーションの受託開発に携わる。
|
「.NET開発者中心 厳選ブログ記事」 |
- 第2回 簡潔なコーディングのために (2017/7/26)
ラムダ式で記述できるメンバの増加、throw式、out変数、タプルなど、C# 7には以前よりもコードを簡潔に記述できるような機能が導入されている - 第1回 Visual Studio Codeデバッグの基礎知識 (2017/7/21)
Node.jsプログラムをデバッグしながら、Visual Studio Codeに統合されているデバッグ機能の基本の「キ」をマスターしよう - 第1回 明瞭なコーディングのために (2017/7/19)
C# 7で追加された新機能の中から、「数値リテラル構文の改善」と「ローカル関数」を紹介する。これらは分かりやすいコードを記述するのに使える - Presentation Translator (2017/7/18)
Presentation TranslatorはPowerPoint用のアドイン。プレゼンテーション時の字幕の付加や、多言語での質疑応答、スライドの翻訳を行える
|
|
- - PR -