Skip to content

Instantly share code, notes, and snippets.

@matarillo
Created November 23, 2024 00:05
Show Gist options
  • Save matarillo/df5f079ec24eda7275a16ef64553c8ec to your computer and use it in GitHub Desktop.
Save matarillo/df5f079ec24eda7275a16ef64553c8ec to your computer and use it in GitHub Desktop.
Title Author Date
GUIアーキテクチャ
Martin Fowler
18 July 2006

GUIアーキテクチャ

原文: GUI Architectures (martinfowler.com)

リッチ・クライアント・システムのコードを組織化するのには多くの異なる方法があった。ここでは、最も影響力があったと感じるものを選んで述べ、それらがどのようにパターンに関係しているかを説明する。

最後の有意な更新:06年7月18日

目次

ユーザーとしても開発者としても、グラフィカル・ユーザ・インタフェースはソフトウェアの景色の一部でなじみ深いものになった。 設計の観点から見ると、それはシステム設計の問題の特定の集合――異なるが類似したいくつかの解決策を導く問題――を表わす。

私の関心は、リッチ・クライアント開発でアプリケーション開発者たちが使う共通で役に立つパターンを見出すことである。 私は、プロジェクト・レビューでいろいろな設計を見てきたし、より安定した方法で書かれているいろいろな設計も見てきた。 これらの設計の内部に役に立つパターンがある。しかし、それらを説明することはしばしば簡単ではない。 例としてModel-View-Controllerを挙げる。 これはパターンとしてしばしば参照されるが、相当な数の異なる考えを含むので、それをパターンとみなすことが本当に役に立つかは私にはわからない。 さまざまな人がさまざまなところでMVCについて読み、さまざまな考えを受け取って、それらを「MVC」だと言う。 これが十分な混乱を引き起こさないとしても、伝言ゲームのシステムによって発展するMVCへの誤解の影響を受ける。

このエッセイでは私は、いくつかの面白いアーキテクチャを探検し、それらの最も面白い特徴について私の解釈を説明したい。 私の望みは、私が説明するパターンを理解するための文脈をこのエッセイが提供することである。

このエッセイはある程度、長年にわたる複数のアーキテクチャを通してUI設計の考え方をたどる、ある種の知的な歴史とみなすことができる。 しかし、私はこれについて注意を発しなければならない。 アーキテクチャを理解することは簡単ではない。それらの多くが変化して死んでいく時にはなおさらだ。 人々が同じアーキテクチャから異なるものを読み取るので、考えの広がりをたどることはさらに難しい。 特に、私は説明するアーキテクチャの徹底的な調査をしなかった。 私がしたことは、設計の一般的な説明と言われるものだ。 それらの説明が何かを省略していたとしても、私はそれを全く気にしない。 だから、アーキテクチャの信頼できる解説として私の説明を受け入れてはいけない。 さらに、特に関連すると思わなかったときに、私が無視または単純化したものがある。 私の主要な関心は根底にあるパターンにあり、これらの設計の歴史にはないことを忘れないように。

(ちょっとした例外がある。というのは、私はMVCを調べるために動作しているSmalltalk-80にアクセスした。 繰り返すが、私は調査結果を網羅的に説明するつもりはなかった。とはいえ、それは一般的な説明では成せなかったことを明らかにした――そのことが、私がここでしている他のアーキテクチャの説明について、私をさらに慎重にしさえする。 もしあなたがこれらのアーキテクチャの1つをよく知っていて、重要なことについて私が間違っていたり欠けていたりしているのに気が付いたら、教えてほしい。 この領域のより徹底的な調査はアカデミックな研究の良い対象であるとも思っている。)

フォームとコントロール

この調査を、単純でよく知られているアーキテクチャから始めよう。 それには通称がないので、このエッセイでは「フォームとコントロール」と呼ぼう。 これは90年代のクライアント・サーバー開発環境――Visual Basic、Delphi、Powerbuilderといったツール――によって奨励されたものなので、おなじみのアーキテクチャである。 私のような設計おたくによってしばしば中傷されたりもするが、一般的に用いられ続けている。

これを調査するために、そして実際には他のアーキテクチャを調査するためにも、一般的な例を使おう。 私が住んでいるニューイングランドには、空気中のアイスクリームの微粒子の量を観測する政府計画がある。 濃度が低すぎるなら、これは我々が十分なアイスクリームを食べていないことを示す――それは我々の経済と社会秩序に深刻な危険をもたらす。 (私は、この手の本で通常見られるのに負けず劣らず現実的な例を使うのが好きだ。)

我々のアイスクリーム健康状態を監視するために、政府はニューイングランド全州に監視ステーションを建てた。 複雑な大気のモデリングを使用して、当局は監視ステーションごとに目標値を設定する。 時々、職員はいろいろなステーションに行って、実際のアイスクリーム粒子の濃度を書き記す評価に出かける。 このUIでは、ステーションを選んで、日付と実測値を入力できる。 するとシステムは、目標値との相違を計算して表示する。 さらに、実測値が目標値より10%以上低ければ相違は赤でハイライトされ、5%以上高ければ緑でハイライトされる。

図1: 私が例として使うUI

図1: 私が例として使うUI

この画面を見れば、我々がまとめたように、重要な部分があるのを見ることができる。 フォームは、我々のアプリケーションに固有のものだが、一般的なコントロールを使っている。 大部分のGUI環境には、実際にアプリケーションで使うことができる一般的なコントロールがたくさん同梱されている。 我々は新しいコントロールを自分で造ることができ、それはしばしば良い考えだが、一般的な再利用できるコントロールと特定のフォームにはやはり違いがある。 特別に書かれたコントロールさえ、複数のフォームにわたって再利用されることがありえる。

フォームは、2つの主要な責務を含む:

  • 画面レイアウト: 画面上のコントロールの配置と、他者との階層構造を定める。
  • フォーム・ロジック: 簡単にはコントロール自身の内部にプログラムできないふるまい。

大部分のGUI開発環境では、開発者はフォームの空いている所にコントロールをドラッグ&ドロップすることができるグラフィカル・エディタを使って画面レイアウトを定義できる。 これは、フォーム・レイアウトのほとんどを取り扱う。 フォーム上のコントロールの心地よいレイアウトを設定するのは、このように簡単である(必ずしも最高のやり方ではないが――この件は後で取り上げるつもりだ)。

コントロールはデータを表示する――測定値に関するこのケースでは。 このデータはほとんど常に他のどこかから来るだろう。今回はSQLデータベースだと仮定しよう。こういったクライアント・サーバー・ツールの大部分が想定している環境であるので。大部分の状況では、関係するデータには3つのコピーがある:

  • データのコピーの1つは、データベースそのものにある。このコピーはデータの長期的な記録であるので、私はそれを 記録状態 と呼ぶ。記録状態は、通常いろいろなメカニズムによって共有され、複数の人々の目に見える。
  • 更なるコピーは、アプリケーション内のメモリ上のレコードセットにある。クライアント・サーバー環境の大部分はこれを簡単にできるようにするツールを提供した。このデータはアプリケーションとデータベースとの間の特定のセッションの間だけ関係するので、私はそれを セッション状態 と呼ぶ。基本的に、これは保存するかコミットしてデータベースへ戻すまでの間にユーザーが扱うデータの一時的なローカル・バージョンを提供する――その時には、それは記録状態とマージされる。記録状態とセッション状態を調整することに関する問題についてはここでは気にしない:私は、P of EAAでいろいろな技術を取り上げた。
  • 最後のコピーはGUIコンポーネント自体にある。これは、厳密に、画面で見られるデータである。それゆえに、私はそれを 画面状態 と呼ぶ。画面状態とセッション状態をどのように同期させておくかは、UIにとって重要である。

画面状態とセッション状態を同期させておくことは、重要な仕事である。これをより簡単にするのを助けるツールは、データ・バインディングであった。その考えとは、コントロール・データまたは根底にあるレコードセットのどちらかにどのような変化があってもすぐにもう一方に伝播されるというものだった。よって、もし画面上の実測値を変えたならば、テキスト・フィールド・コントロールは根底にあるレコードセットの正しいコラムを効果的に更新する。

一般に、データ・バインディングは注意を要する。コントロールの変化がレコードセットを変え、それがコントロールを更新し、それがレコードセットを更新する……という循環を避ける必要があるのなら。 使用のフローはこれを避けるのを助ける――画面が開かれるときにセッション状態から画面までロードし、その後は、画面状態へのどんな変化もセッション状態へ伝播する。 一旦画面が立ち上がったならば、セッション状態が直接更新されることはあまりない。 その結果、データ・バインディングが全体的に双方向にはならないかもしれない――最初のアップロードと、その後で変化がコントロールからセッション状態に伝播するだけに限定される。

データ・バインディングは、クライアント・サーバー・アプリケーションの多くの機能をかなりうまく取り扱う。 実測値を変えたときにはコラムが更新される。それが選択されたステーションを変えるだけであってもレコードセットの現在選択行を変え、他のコントロールのリフレッシュを引き起こす。

このふるまいの多くはフレームワーク構築者によって組み込まれている。彼らは一般のニーズを見て、それを満たすのを簡単にするのだ。 特に、これはコントロールに(通常プロパティと呼ばれている)値を設定することで実現される。 コントロールは、単純なプロパティ・エディタによってコラム名を設定されることで、レコードセットの特定のコラムと結びつく。

正しい種類のパラメーター化とともにデータ・バインディングを使うことで、長い道を進むことができる。 しかし、どこまでも進めるというわけではない――たいてい、パラメーター化の選択肢には適さないだろうロジックが少しはある。 今回の場合は相違を計算することが、この組み込まれたふるまいに適合しないものの例である――これはアプリケーションに固有であるから、通常はフォームに置かれる。

これが動作するために、実測値フィールドの値が変わるときはいつでも、フォームは通報される必要がある。そして、フォーム上の固有のふるまいをいくつか呼び出すことが一般的なテキスト・フィールドに要求される。 これは、クラス・ライブラリを導入し、制御の反転として呼び出されることでそれを使うことよりも少しだけ複雑なことだ。

この手のものを動作させるにはいろいろなやり方がある――クライアント・サーバー・ツールキットによくあるものはイベントの概念であった。 各々のコントロールは、起こすことができるイベントの一覧を持っていた。 どんな外部オブジェクトでもイベントに興味があるとコントロールに伝えることができた――そうすると、コントロールはイベントが上がったときにその外部オブジェクトを呼び出す。 これは基本的に、フォームがコントロールを監視しているというオブザーバー・パターンの言い変えでしかない。 フォームの開発者がイベントが起きたときに呼び出されるサブルーチンにコードを書くことができる何らかの仕組みは、通常はフレームワークが提供した。 イベントとルーチンの間の関連が正確にはどのように作られたかはプラットフォーム間で異なっていたので、この議論においては重要でない――重要なのはそれが起きるための仕組みがいくつか存在したということである。

一旦フォーム内のルーチンに制御がわたれば、必要なことは何でもできる。 それは特定のふるまいを実行し、必要に応じてコントロールを修正し、データ・バインディングによってそれらの変更すべてをセッション状態へ伝播させることもできる。

データ・バインディングは必ず存在するわけではないので、これも必要である。 Windowsコントロールの大きな市場があるが、全てがデータ・バインディングをするというわけではない。 データ・バインディングが存在しないならば、同期を行うのはフォーム次第である。 これは、保存ボタンが押されたとき、まず最初にウィジェットに設定されたレコード・セットからデータを引き抜いて、変更されたデータをレコード・セットへコピーするという形で動作することもできた。

データ・バインディングが存在するとして、実測値を編集する場合を調べよう。 フォーム・オブジェクトは、一般的なコントロールへの直接の参照を保持している。 参照は画面上のコントロールごとにあるだろうが、ここでは実測値、相違、目標値フィールドにだけにしか興味がない。

図2:フォームとコントロールのクラス図

図2:フォームとコントロールのクラス図

初期化中にフォームが画面を組み立てるとき、フォームは自分自身をイベントに登録し、自身のメソッドと結びつける――ここでは actual_textChanged メソッドだ。

図3:フォームとコントロールでジャンルを変更するためのシーケンス図

図3:フォームとコントロールでジャンルを変更するためのシーケンス図

ユーザーが実測値を変えるとき、テキスト・フィールド・コントロールはイベントを起こし、フレームワーク・バインディングの魔法によって actual_textChanged が実行される。 このメソッドは実測値および目標値のテキスト・フィールドからテキストを取得して、引き算をし、値を相違フィールドに入れる。 またこのメソッドは、値がどんな色で表示されなければならないかを算出し、テキスト・カラーを適切に調節する。

いくつかのサウンドバイトでアーキテクチャをまとめることができる:

  • 開発者は、一般的なコントロールを使ってアプリケーション固有フォームを書く。
  • フォームは、フォーム上のコントロールのレイアウトを定義する。
  • フォームはコントロールを監視していて、関心があるイベントがコントロールから上げられるのに反応するハンドラー・メソッドを持っている。
  • 単純なデータ編集は、データ・バインディングを通じて扱われる。
  • 複雑な変更は、フォームのイベント処理メソッドでなされる。

Model View Controller

おそらく、UI開発パターンにおいて最も広く引用されているものはModel View Controller(MVC)であろう――しかも最も誤って引用されている。 私は、MVCとして説明されているものがまったく似つかないものだとわかったことが何回あったか忘れてしまった。 率直に言って、この理由の多くは、古典的なMVCの一部が近年のリッチ・クライアントにはあまり意味をなさないということである。 しかし今回は、その起源を見てみることにする。

MVCを見たとき、これがどんな規模においても最初に重大なUI研究を試みた1つであったことを思い出すことは重要である。 グラフィカルユーザーインターフェイスは、70年代には必ずしも一般的ではなかった。 私が今しがた説明したフォームとコントロール・モデルは、MVCの後に現れた――私がそれを最初に説明したのは、より単純だからであって、常に良いやり方というわけではない。 また、私は評価の例を使ってSmalltalk 80のMVCを論じるつもりである――しかし、そうするためにSmalltalk 80の実際の細部をいくつか勝手にさせてもらっていることをご了承いただきたい――それは最初は白黒システムであった。

MVCの中心であり、そして後のフレームワークに最も影響力があった考えは、私がSeparated Presentationと呼んでいるものである。 Separated Presentationを支える考えは、現実の世界の認識をモデル化するドメイン・オブジェクトと、画面で見えるGUIの要素であるプレゼンテーション・オブジェクトとの間を明確に分割することだ。 ドメイン・オブジェクトは完全に自己完結していて、プレゼンテーションの参照なしで動作しなければならない。また、おそらく同時に、複数のプレゼンテーションをサポートすることができなければならなくもある。 このアプローチはUnix文化の重要な部分でもあって、今日でも多くのアプリケーションがグラフィックおよびコマンド・ラインの両方のインターフェースによって操作できるようにし続けている。

MVCにおいて、ドメイン要素はモデルと呼ばれる。 モデル・オブジェクトは、UIを完全に知らない。 評価UIの例を検討し始めるために、モデルは測定値であり、興味があるデータすべてのフィールドを持っているとしよう。 (すぐにわかるように、リストボックスが存在することで、何がモデルであるかという問題がむしろやや複雑になるが、しばらくの間はそのリストボックスを無視することにする。)

MVCにおいて、私はフォームとコントロールで持っていたレコード・セットの概念ではなく、通常のオブジェクトがDomain Modelであることを想定している。 これは、設計の背後にある一般的な仮定を反映している。 フォームとコントロールでは大部分の人々がリレーショナル・データベースのデータを楽に操作したいと思っていることを仮定していたが、MVCでは通常のSmalltalkオブジェクトを操作していることを仮定する。

MVCのプレゼンテーション部分は、残り2つの構成要素でできている:ビューとコントローラである。 コントローラの職務は、ユーザーの入力を受け入れて、それをどうするべきかを判断することである。

ここで私が強調しなければならないことは、ただ1つのビューとコントローラしかないのではなく、画面の構成要素ごと、コントロールごと、そして画面全体に対してビュー・コントローラのペアを持っていることだ。 それで、ユーザーの入力に反応する最初の部分は、誰が編集されたかを知るために協調しているいろいろなコントローラである。 このケースではそれは実測値テキストフィールドであり、それゆえ次に起こることを処理するのはテキスト・フィールド・コントローラになるだろう。

図4:モデル、ビュー、コントローラに必須の依存関係

図4:モデル、ビュー、コントローラに必須の依存関係。(必須と呼んでいるのは、実際にはビューとコントローラがお互いと直接つながっているからだが、開発者は通常この事実を利用しない。)

後の環境と同様に、Smalltalkでは再利用することができる一般的なUIコンポーネントが望まれるとわかった。 この場合、そのコンポーネントとはビュー・コントローラのペアになるだろう。 両方とも一般的なクラスで、同様に、アプリケーション固有のふるまいに接続される必要があった。 フォームとコントローラにおけるフォームに類似した意味で、画面全体を表現し、より低レベルなコントロールのレイアウトを定義するであろう評価ビューがあるだろう。 しかしフォームとは異なり、MVCはより低レベルなコンポーネントのためのイベント・ハンドラを評価コントローラには持っていない。

図5:アイスクリームのモニター表示のMVC版のクラス

図5:アイスクリームのモニター表示のMVC版のクラス

テキスト・フィールドの構成は、モデル(測定値)とのつながりを与え、テキストが変わるときにどんなメソッドを実行するべきかについて伝えることから来る。 これは、画面が初期化される時に #actual: にセットされる(Smalltalkでは、先行する # はシンボルまたは拘束された文字列を示す)。 続いてテキスト・フィールドのコントローラは、変化をもたらすために、測定値上の対応するメソッドをリフレクションで起動する。 基本的に、これはデータ・バインディングで起こるのと同じメカニズムであり、コントロールは根底にあるオブジェクト(列)にリンクされていて、どのメソッド(コラム)を操作するかを伝えられている。

図6:MVCで実測値を変える

図6:MVCで実測値を変える

よって、低レベルのウィジェットを監視している全体的なオブジェクトはないが、その代わりに低レベルのウィジェットがモデルを監視しており、フォームによってなされるであろう決定の多くを自分自身で取り扱う。 この場合、相違を算出することについては、測定値オブジェクト自体が、そうするための自然な場所である。

オブザーバーはMVCで起きることで、実際それはMVCの功績である考え方の1つである。 この場合、すべてのビューとコントローラは、モデルを観察する。 モデルが変わるとき、ビューは反応する。 この場合、実測値のテキスト・フィールド・ビューは、測定値オブジェクトが変わったことを通知され、そのテキスト・フィールドのアスペクトとして定義されたメソッド――この場合 #actual ――を実行して、その値を結果にセットする。 (それは色についてのことに類似した何かをするのだが、これはすぐ後で取り上げるつもりである、自分自身のスペクターを発生させる。)

テキスト・フィールド・コントローラ自身が値をビューにセットしなかったことに気がつくだろう。それはモデルを更新して、続いて監視メカニズムに更新の面倒を見させた。 これは、フォームがコントロールを更新し、根底にあるレコードセットを更新するためにデータ・バインディングに頼るという、フォームとコントロールのアプローチと全く異なっている。 これら2つのスタイルをパターンとして記述する:フロー同期オブザーバー同期。 これらの2つのパターンは、画面状態とセッション状態との同期を開始させることを扱うための異なった方法を記述する。 フォームとコントロールでは、直接更新される必要があるいろいろなコントロールを操作しているアプリケーションの流れを通して、実行される。 MVCでは、モデルを更新し、続いてそのモデルを観察しているビューを更新するためにオブザーバー関係に頼ることによって、実行される。

データ・バインディングが存在しないとき、フロー同期はさらに明らかになる。 アプリケーション自身が同期をする必要があるならば、典型的にはアプリケーションの流れの中の重要なポイント――例えば画面を開いたり、保存ボタンを押したりしたとき――に実行された。

オブザーバー同期の結果の1つは、ユーザーが特定のウィジェットを操作するときには、そのコントローラは他のどのウィジェットを変更する必要があるのかを全く知らないということである。 フォームがものごとを監視していて、ある変化に対して全体的な画面状態を首尾一貫させる必要がある一方で、これは複雑な画面ではかなり大変なことなのだが、オブザーバー同期のコントローラはこういうことを無視することができる。

同じモデル・オブジェクトを見ている複数の画面が開かれていているならば、この役に立つ無知は特に便利になる。 古典的なMVCの例はデータ画面のようなスプレッドシートで、そのデータの異なるグラフが数個、別々のウインドウに置かれているようなものだった。 スプレッドシート・ウインドウは他にどんなウインドウが開いているか知っている必要はなく、単にモデルを変えればオブザーバー同期が残りの面倒を見た。 フロー同期では、リフレッシュするように伝えるために、他にどのウインドウが開いているかについて知る若干のやり方を必要とするだろう。

オブザーバー同期は素晴らしいが、それにも欠点がある。 オブザーバー同期に関する問題はオブザーバー・パターンそのものの根本的な問題である――何が起こっているかについて、コードを読むことでは理解できない。 Smalltalk 80のいくつかの画面がどのように動くか調べようとしたとき、私は非常に力強くこれを思い出した。 コードを読むことによって私はとても遠くまでたどり着くことができたが、一旦監視メカニズムが働き始めたならば、何が起きているか知ることができる唯一の方法はデバッガーとトレース文を通してであった。 オブザーバーのふるまいは潜在的なふるまいであるので、理解してデバッグするのが難しい。

同期へのアプローチの違いは特にシーケンス図を見ることで目立つけれども、最も重要で最も影響力がある違いはMVCでのSeparated Presentationの使用である。 実測値と目標値との相違を計算することはドメインのふるまいであり、UIには何も関係しない。 その結果、以下のSeparated Presentationはこれをシステムのドメイン層の中に置かなければならないと言う――それは間違いなく測定値オブジェクトが表現するものである。 測定値オブジェクトを見れば、相違の機能はユーザ・インタフェースのどんな概念なしででも完全に意味をなす。

しかしこの点で、若干の複雑化が見られるようになる。 MVC論の邪魔になる厄介な点を飛ばした領域が2つある。 最初の問題領域は、相違の色を設定することの取り扱いである。 これをドメイン・オブジェクトに実際に適合させてはならない。値を表示する際の色はドメインの一部ではないからだ。 これに対処することの第一歩は、ロジックの一部がドメイン・ロジックであると理解することである。 ここでしていることは、相違について質的な言明をすることであり、良(5%以上高い)、悪(10%以上低い)、そして標準(残り)と呼ぶことができた。 その評価をすることは確かにドメイン言語であるが、それを色に対応付けて相違フィールドを変えることはビュー・ロジックである。 問題は、このビュー・ロジックをどこに置くかである――これは標準テキスト・フィールドの一部ではない。

この種の問題は初期のSmalltalkersも直面し、彼らは解決案をいくつか生み出した。 私が上で示した解決案は、汚いもの――動かすために、ドメインの純度についていくらか妥協すること――である。 私は臨時の汚い行為は認めよう――しかし、それを習慣にしないように勤める。

フォームとコントロールでなされることはほとんどできた――評価画面ビューに相違フィールド・ビューを監視させ、相違フィールドが変化したときには評価画面が反応して相違フィールドのテキスト・カラーを設定できる。 ここでの問題は、さらに多くの監視メカニズムの使用――そして、それを使えば使うほど、指数的に複雑になっていく――および、さまざまなビューの間の余計な結合を含んでいる。

私が好むやり方は、UIコントロールの新しい型を造ることである。 根本的に必要なものは、ドメインに質的な値を要求し、それを値と色に関する内部テーブルに照らし合わせ、それに応じてフォントの色をセットするようなUIコントロールである。 そのテーブルと、ドメイン・オブジェクトに尋ねるメッセージの両方は、評価ビューが自分自身を組み立てるときに、フィールドがモニターする外観を設定するのと同様に設定されるだろう。 追加のふるまいを加えるためだけにテキスト・フィールドをサブクラス化することが簡単にできるならば、このアプローチは非常によく機能し得る。 これは明らかに、コンポーネントがどれくらいサブクラス化しやすく設計されているか次第である――Smalltalkでは非常に簡単にできた――他の環境ではもっと難しいかもしれない。

図7:色を決定するように構成できるテキスト・フィールドの特別なサブクラスを使用する

図7:色を決定するように構成できるテキスト・フィールドの特別なサブクラスを使用する

最後のルートは、新しい種類のモデル・オブジェクト、つまり画面の近くに置かれているが、ウィジェットからは独立しているモデルを作ることである。 それは、画面に関するモデルになるだろう。 測定値オブジェクトのメソッドと同じメソッドはただ測定値に委任されるが、UIだけに関連するふるまい、例えばテキストの色など、をサポートしたメソッドが追加されるだろう。

図8:ビュー・ロジックを取り扱うために中間のプレゼンテーション・モデルを使う

図8:ビュー・ロジックを取り扱うために中間のプレゼンテーション・モデルを使う

この最後の選択肢は多数のケースでうまく機能して、見て分かるように、後に続くSmalltalkerたちの一般的な道になった――これは実際に設計され、プレゼンテーション層の一部となっているモデルであるので、私はこれをPresentation Modelと呼ぶ。

Presentation Modelは、もう一つのプレゼンテーション・ロジック問題――プレゼンテーション状態――でもうまく機能する。 基本的なMVCの概念は、ビューのすべての状態がモデルの状態から得られると仮定している。 この場合、リストボックスでどのステーションが選択されているかについてはどうすればわかるだろうか? Presentation Modelは、この種の状態を配置するための場所を提供することによって解決する。 データが変わったときだけ使用可能になる保存ボタンがあるならば、類似した問題が起きる――繰り返すが、それはモデルに対する我々のインタラクションについての状態であって、モデルそのものではない。

さて、そろそろMVCについてのサウンドバイトの時間だろう。

  • プレゼンテーション(ビューとコントローラ)とドメイン(モデル)の間を強く分離しなさい――Separated Presentation
  • GUIウィジェットを(ユーザー刺激に反応するための)コントローラと(モデルの状態を表示するための)ビューに分けなさい。コントローラとビューは、(主に)直接ではなくモデルを通して通信しなければならない。
  • ビュー(とコントローラ)にモデルを監視させ、複数のウィジェットを直接通信する必要なしに更新させなさい――オブザーバー同期

VisualWorksアプリケーション・モデル

上で説明したように、Smalltalk 80のMVCは非常に影響力があり、優れた特徴もあったが、誤りもあった。 80年代と90年代にSmalltalkが発達するにつれて、古典的なMVCモデルの重要なバリエーションがいくつか現れることとなった。 もしビュー/コントローラの分離がMVCに不可欠な部分であると考えるならば、MVCは消えてしまったと言っても実は過言ではない――名前が意味するのはそういうことだ。

MVCで明らかに役に立ったものは、Separated Presentationオブザーバー同期であった。 そう、Smalltalkが発達したのにつれて、これらはとどまった――実際に多くの人々にとってそれらはMVCの重要な構成要素であったのだ。

近年はSmalltalkもバラバラになった。 (最小限の)言語定義といったSmalltalkについての基本的な考えは同じままだったが、複数のSmalltalkが異なるライブラリで発展するのを見た。 いくつかのライブラリがネイティブ・ウィジェット、すなわちフォームとコントロール・スタイルで用いられるコントロールを使い始めるにつれて、これはUIの展望から重要になった。

Smalltalkは当初Xerox Parc研究所で開発され、Smalltalkを市場に出して発展させるための別の会社(ParcPlace)を分離独立した。 ParcPlace SmalltalkはVisualWorksと呼ばれ、クロスプラットフォーム・システムであることを重視していた。 Javaのずっと前に、Windowsで書かれたSmalltalkプログラムを取ってきて、それをそのままSolaris上で動かすことができた。 その結果、VisualWorksはネイティブ・ウィジェットを使わず、GUIを完全にSmalltalkの範囲内に保った。

MVCに関する議論において、私はMVCの若干の問題――特にビュー・ロジックとビュー状態に対処する方法――を残した。 VisualWorksは、これに対処するためにApplication Modelと呼ばれている構成概念――Presentation Modelの方へ進む構成概念――を生み出すことで、自身のフレームワークを純化した。 Presentation Modelのようなものを使うというアイデアは、VisualWorksでは新しくなかった――最初のSmalltalk 80コード・ブラウザーは非常に類似していたが、VisualWorks Application Modelは、それを完全にフレームワークに焼き固めた。

この種のsmalltalkの重要な構成要素は、プロパティをオブジェクトに変えるというアイデアであった。 プロパティをもつオブジェクトの普通の概念において、氏名と住所のためのプロパティを持っているPersonオブジェクトについて考える。 これらのプロパティはフィールドであるかもしれないが、何か他のものでもよかった。 プロパティにアクセスするための標準的な慣例が通常は存在する: Javaでは、 temp = aPerson.getName()aPerson.setName("martin") を見かけるだろうし、C#では、 temp = aPerson.nameaPerson.name = "martin" となるだろう。

Property Object は、実際の値をラップするオブジェクトを返すプロパティを持っていることでこれを変える。 よってVisualWorksでは、名前を要求すると、ラッピング・オブジェクトが戻される。 我々は、続いて、ラッピング・オブジェクトにその値を要求することによって、実際の値を得る。 したがって、人名にアクセスする時は temp = aPerson name value および aPerson name value: 'martin' となるだろう。

Property Objectは、ウィジェットとモデルの間のマッピングを少し簡単にする。 対応するプロパティを得るために送るメッセージをウィジェットに教えなければならない。そして、ウィジェットは value および value: を使って適切な値にアクセスすることを知っている。 VisualWorksのプロパティ・オブジェクトでも、onChangeSend: aMessage to: anObserver メッセージでオブザーバーを構成することができる。

Visual Worksには、プロパティ・オブジェクトと呼ばれているクラスは実は存在しない。 その代わりに、多くのクラスが、value / value: / onChangeSend: プロトコルに従っていた。 最も単純なものはValueHolderである――これは自分自身の値を保持する。 この議論への関連性がさらに高いのはAspectAdaptorである。 AspectAdaptorでは、プロパティ・オブジェクトが別のオブジェクトのプロパティを完全にラップできた。 こうして、以下のようなコードで、PersonオブジェクトのあるプロパティをラップしたPersonUIクラスのプロパティを定義できた。

adaptor := AspectAdaptor subject: person
adaptor forAspect: #name
adaptor onChangeSend: #redisplay to: self

それでは、アプリケーション・モデルが動作中の例にどう適合するかについて見てみよう。

図9:動作中の例に関するVisual Worksアプリケーション・モデルについてのクラス図

図9:動作中の例に関するVisual Worksアプリケーション・モデルについてのクラス図

アプリケーション・モデルを使うことと古典的MVCを使うことの主な違いは、前者はドメイン・モデル・クラス(リーダー)とウィジェットの間に中間のクラスを持っていることである――これがアプリケーション・モデル・クラスである。 ウィジェットはドメイン・オブジェクトに直接アクセスしない――ウィジェットのモデルはアプリケーション・モデルである。 ウィジェットはまだビューとコントローラに分解されるが、新しいウィジェットを造るのでない限り、その区別は重要でない。

UIを組み立てるときはUIペインターで作業するが、その際にペインターでは各々のウィジェットのアスペクトを設定する。 アスペクトは、プロパティ・オブジェクトを返すアプリケーション・モデルのメソッドに対応する。

図10:実測値を更新するとどのように相違テキストが更新されるかについて示しているシーケンス図

図10:実測値を更新するとどのように相違テキストが更新されるかについて示しているシーケンス図

図10は、基本的な更新シーケンスがどのように機能するかについて示す。 テキスト・フィールドの値を変えると、そのフィールドはアプリケーション・モデルの内側でプロパティ・オブジェクトの中の値を更新する。 その更新は根底にあるドメイン・オブジェクトにまで届き、その実測値を更新する。

ここで監視関係が作動する。実測値を更新すると、変更したことを測定値に示せるように、いろいろと組み立てる必要がある。 測定値オブジェクトが変わった――特に、相違の外観が変わった――ことを実測値が示すために、変更する側に呼び出しを置く。 相違のアスペクト・アダプターを組み立てるときに、測定値を監視し、更新メッセージを取り上げ、続いてテキスト・フィールドに送り届けるよう指示するのは簡単である。 続いてテキスト・フィールドは、再びアスペクト・アダプターによって、新しい値を得ることを始める。

アプリケーション・モデルとプロパティ・オブジェクトをこのように使うことで、多くのコードを書くことなく更新を配線することを支援できる。 これも、きめが細かい同期(私は良いことであるとは思わないが)をサポートする。

アプリケーション・モデルによって、真のドメイン・ロジックからUIに特有のふるまいと状態を切り離すことができる。 よって、前に言及した問題のうちの1つである、リスト中の現在選択されている項目を保持することについては、ドメイン・モデルのリストをラップして、かつ現在選択されている項目を保存する、特定の種類のアスペクト・アダプターを用いることで解決できる。

しかし、これによる制限は、さらに複雑なふるまいのためには、特別なウィジェットとプロパティ・オブジェクトを造る必要があるということである。 一例として、提供されたオブジェクトの集合は、相違のテキスト色と相違の程度とを結び付ける手段を提供しない。 アプリケーションとドメイン・モデルを切り離すことで、意思決定を正しいやり方で切り離すことができる。しかし、アスペクト・アダプターを監視するウィジェットを使うためには、新しいクラスをいくつか作る必要がある。 しばしば、これは作業が多すぎるとみなされたので、図11の場合のように、アプリケーション・モデルが直接ウィジェットにアクセスするのを許すことによって、この手のことをより簡単にすることができた。

図11:アプリケーション・モデルが直接ウィジェットを操作することによって色を更新する

図11:アプリケーション・モデルが直接ウィジェットを操作することによって色を更新する

このようにウィジェットを直接更新することはPresentation Modelの一部分ではない。そして、それはVisual Worksアプリケーション・モデルが真のPresentation Modelではない理由である。 この、ウィジェットを直接操作することが必要なことは、多くの人によってちょっと汚い回避方法とみなされ、Model-View-Presenterアプローチが発展するのを助けた。

さて、それではApplication Modelのサウンドバイトだ。

  • Separated Presentationオブザーバー同期を使うことでMVCに従った。
  • プレゼンテーション・ロジックと状態の収納場所――Presentation Modelの部分的な発展――として中間のアプリケーション・モデルを導入した。
  • ウィジェットはドメイン・オブジェクトを直接監視せず、その代わりにアプリケーション・モデルを監視する。
  • いろいろな層をつなぐのを助け、オブザーバーを用いたきめの細かい同期をサポートするために、Property Objectsを広範囲に利用した。
  • アプリケーション・モデルがウィジェットを操作することはデフォルトのふるまいではなかったが、複雑なケースでは一般的になされた。

Model-View-Presenter (MVP)

MVPは、最初はIBMで登場し、1990年代のTaligentでより目にするようになったアーキテクチャである。 それは、Potelの論文から最も広く引用された。 この考えは、Dolphin Smalltalkの開発者たちによってさらに普及し、解説された。 見ればわかるように、2つの説明は完全にかみ合うというわけではないが、それの元となる基本的な考えは有名になった。

MVPに取り組むためには、UI研究の2つの要素にある重大なミスマッチについて考えることが役に立つとわかった。 一方では、UI設計における主流のアプローチだったのはフォームとコントローラ・アーキテクチャである。他方では、それはMVCおよびその派生物である。 フォームとコントロール・モデルは、理解するのが簡単で、再利用可能なウィジェットとアプリケーション固有コードをうまく分離するような設計を提供する。 それに不足するもの、そしてMVCが強力に持っているものは、Separated Presentationと、実のところ、Domain Modelを使うプログラミングのコンテキストである。 私は、MVPとは両方の潮流を結びつけていいとこ取りをしようとするためのステップだと理解している。

Potelの最初の構成要素はビューをウィジェットの構造とみなすことことである、ウィジェットはフォームとコントロール・モデルのコントロールに対応し、どんなビュー/コントローラ分離でも取り除く。 MVPのビューは、こうしたウィジェットの構造である。 それには、ウィジェットがユーザー・インタラクションに反応する方法を記述するようなふるまいは全く含まれない。

ユーザーの行為に対する活発な反応は、別のプレゼンター・オブジェクトに置かれる。 ユーザー・ジェスチャーの基本的なハンドラーはまだウィジェットに存在するが、これらのハンドラーは単にプレゼンターに制御を渡すだけである。

プレゼンターは、続いて、イベントに反応する方法を決定する。 Potelは主にモデルに及ぼす働きに関してこのインタラクションを検討し、命令と選択のシステムによって実現する。 ここで強調すべき役に立つことは、モデルの編集をすべて命令で包むアプローチである――これはアンドゥ/やり直しのふるまいを提供する良い基盤を提供する。

プレゼンターがモデルを更新するとき、ビューはMVCが使うのと同様のオブザーバー同期アプローチによって更新される。

Dolphinの説明も類似している。 繰り返しになるが、主要な類似性は、プレゼンターの存在である。 Dolphinの説明では、命令と選択を通してモデルに作用するプレゼンターの構造は存在しない。 ビューを直接操作するプレゼンターもはっきり議論されている。 Potelはプレゼンターがこうすべきかどうかについて話していないが、Dolphinにとっては、変化フィールドのテキストに色をつけることを厄介にしていたようなApplication Modelの欠点を解決するためにこの能力は必須だった。

MVPのことを考える時のバリエーションの1つは、プレゼンターがビューのウィジェットを制御する度合いである。 一方では、すべてのビュー・ロジックがビューに残され、モデルを描画する方法を決定することにプレゼンターが関係していないケースがある。 このスタイルは、Potelによって暗示されるものである。 BowerとMcGlashanに続く方向は私がSupervising Controllerと呼んでいるものだが、ビューは宣言的に記述されたビュー・ロジックを多く取り扱い、続いてプレゼンターが、より複雑なケースを取り扱うために現れる。

プレゼンターにウィジェットのすべての操作をさせる方向に、ずっと動くこともできる。 このスタイルは、私はPassive Viewと呼んでいるけれども、MVPの元々の説明の一部ではないのだが、人々がテスト可能性の問題を調査するにつれて開発された。 そのスタイルについては後で話すつもりだが、そのスタイルはMVPの味つけの1つである。

前に検討したものとMVPとを対比する前に、ここで挙げた両方のMVP論文が同じようにしている――ただし、私の解釈と全く同じではない――ことについて言及しなければならない。 PotelはMVCのコントローラが全体的なコーディネーターだったと暗に言う――それは私が理解している方法ではない。 Dolphinは、MVCの問題についてたくさん話しているが、MVCという言葉で彼らが言っているものは、私が説明した古典的なMVCというよりもVisualWorksのApplication Model設計を意味している(私はそれを責めたりしない――現在、古典的なMVCに関する情報を得ようとするのは簡単でない。だからほうっておこう。)

それではここでちょっと比較してみよう:

  • フォームとコントロール:MVPはモデルを持っており、プレゼンターはオブザーバー同期でこのモデルを操作し、続いてビューを更新することになっている。ウィジェットへの直接のアクセスは許されるが、これはモデルを使うついでであるべきで、最初の選択であってはいけない。
  • MVC:MVPは、モデルを操作するためにSupervising Controllerを使う。ウィジェットは、Supervising Controllerにユーザー・ジェスチャーを渡す。ウィジェットはビューとコントローラに分けられていない。プレゼンターは、コントローラのようだけれども、ユーザー・ジェスチャーの最初の取扱いがないものとして考えることができる。しかし、プレゼンターが一般的に、ウィジェット・レベルではなくフォーム・レベルにある点に注意することも重要である――これはおそらくさらに大きな違いである。
  • アプリケーション・モデル:ビューはアプリケーション・モデルにするように、プレゼンターにイベントを渡す。しかし、ビューはドメイン・モデルから直接それ自身を更新するかもしれない。プレゼンターはPresentation Modelと同じようには動かない。さらに、プレゼンターはオブザーバー同期に合わないふるまいのために自由にウィジェットに直接アクセスしてよい。

MVPのプレゼンターとMVCのコントローラには明らかな類似点があり、プレゼンターはMVCのコントローラのゆるい形である。 その結果、多くの設計はMVPスタイルに従うだろうが、「コントローラ」をプレゼンターの同義語として使うだろう。 ユーザー入力を取り扱うことについて話しているとき、コントローラを一般に使うことに対する理にかなった議論がある。

図12:MVPで実測値を更新するシーケンス図

図12:MVPで実測値を更新するシーケンス図

アイスクリーム・モニター(図12)のMVP(Supervising Controller)バージョンを見てみよう。 それは多くをフォームとコントロール版と同じように始める――実測値のテキストが変わるときにそのテキスト・フィールドはイベントを起こし、プレゼンターはこのイベントを聞いてフィールドの新しい値を得る。 ここでプレゼンターは測定値ドメイン・オブジェクトを更新する。そして、相違フィールドはそれを監視して、そのテキストを更新する。 最後の部分は色を相違フィールドに設定することである。そして、それはプレゼンターによってされる。 プレゼンターは測定値から色の分類を得て、そして相違フィールドの色を更新する。

さて、MVPのサウンドバイトである:

  • ユーザー・ジェスチャーはウィジェットによってSupervising Controllerに渡される。
  • プレゼンターは、ドメイン・モデルの変更を調整する。
  • MVPのさまざまな変種はビュー更新の扱いが異なる。これらは、オブザーバー同期を使ってプレゼンターにすべての更新を多くの細かな中間者と一緒にさせることからは違ってくる。

控えめなビュー

ここ数年は、自己テスト・コードを書く強い流行があった。 流行センスについて聞くべきでない人ではあるのだが、これは私が完全に没頭している動きである。私の同僚の多くは、xUnitフレームワーク、自動化された回帰テスト、テスト駆動開発、継続的統合といった流行語の大ファンである。

人々が自己テスト・コードについて話すとき、ユーザ・インタフェースが問題としてすかさず頭をもたげる。 多くの人々が、GUIのテストは困難と不可能の間にあると感じる。 これは、主にUIが全体的なUIの環境にきつく結合していて、ほぐして個々にテストするのが難しいからである。

このテストが困難な性質は時々誇張される。 しばしば、ウィジェットをつくってテスト・コードで操作することによって、驚くほど遠くまでたどり着くことができる。 しかし、これが不可能な場合もある。重要なインタラクションを逃したり、スレッドの問題があったり、テストがあまりに遅くて動かせなかったりする。

その結果、テストしにくいオブジェクトのふるまいを最小にするような方法でUIを設計する安定した運動がでてきた。 Michael Feathersは、The Humble Dialog Boxでこのアプローチを小気味よく要約した。 Gerard Meszarosはこの概念を Humble Object という概念に一般化した――どんなオブジェクトでもテストするのが難しいなら最小のふるまいをしなければならない。 そのようにして、それをテスト・スイートに含めることができない場合でも、気付かれなかった失敗の危険を最小にする。

Humble Dialog Box論文ではプレゼンターを利用しているが、元々のMVPより非常に深いやり方で利用する。 プレゼンターは、ユーザー・イベントに反応する方法を決定するだけでなく、UIウィジェット自体にあるデータの集団を取り扱いもする。 その結果、ウィジェットはもはやモデルへの可視性を持ってもおらず必要ともしない; それらはPassive Viewを形成し、プレゼンターによって操られる。

これは、UIを控え目にする唯一のやり方でない。 もう一つのアプローチはPresentation Modelを使うことだが、そうするとウィジェットにもう少しふるまいを必要とする。ウィジェットが自分自身をPresentation Modelに対応付ける方法を知ることができれば足りる。

両方のアプローチの鍵は、プレゼンターをテストすることによって、または、プレゼンテーション・モデルをテストすることによって、テストが難しいウィジェットに触れる必要なく、UIの危険性の大部分をテストするということである。

Presentation Modelでは、なされる実際の意思決定のすべてがPresentation Modelに持たれることで実現される。 すべてのユーザー・イベントと表示ロジックはPresentation Modelに送られる。そのため、ウィジェットがしなければならないことは自分自身をPresentation Modelのプロパティに対応付けることだけである。 そうすれば、どんなウィジェットも表示することなくPresentation Modelの大部分のふるまいをテストすることができる――唯一残る危険はウィジェット・マッピングにある。 これが単純であるならば、それをテストしないことで生きていくことができる。 この場合、画面はPassive Viewアプローチの時ほど控え目ではないが、差は少ない。

Passive Viewはウィジェットをすっかり控え目で、表示する対応付けさえ不要にするので、Passive ViewPresentation Modelにあるわずかな危険さえ除く。 しかし、そのコストは、テストを動かしている間、画面のふりをするTest Doubleを必要とするということである――これは構築する必要がある余分の機構である。

類似したトレードオフが、Supervising Controllerに存在する。 ビューに単純なマッピングをさせることは、いくらか危険を持ち込むことになるが、単純なマッピングを宣言的に指定することができる(Presentation Modelと同様の)利点もある。 さらに複雑な更新がPresentation Modelで決定されて対応付けられる一方で、Supervising Controllerは複雑なケースにおいてどんな対応付けも含まずにウィジェットを操作するので、Supervising ControllerではPresentation Modelよりもマッピングが小さい傾向があるだろう。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment