GUIアーキテクチャ
リッチクライアントシステム用のコードを整理する方法は数多く存在します。ここでは、私が最も影響力があったと感じるものをいくつか取り上げ、それらがパターンとどのように関連しているかを紹介します。
2006年7月18日
これは、私が2000年代半ばに行っていた「さらなるエンタープライズアプリケーションアーキテクチャの開発」という執筆活動の一部です。残念ながら、その後、他のことに気を取られることが多くなり、それ以上取り組む時間がありませんでした。また、近い将来も時間を見つけることが難しいと考えています。そのため、この資料はあくまでドラフト版であり、再度取り組む時間を見つけるまで、修正や更新を行うことはありません。
グラフィカルユーザーインターフェースは、ユーザーとしても開発者としても、私たちのソフトウェアの世界で身近なものになっています。設計の観点から見ると、それらはシステム設計における特定の問題群を表しており、その問題に対して、異なるが類似した解決策が数多く生まれています。
私の関心は、リッチクライアント開発において、アプリケーション開発者が使用するための共通で有用なパターンを特定することです。プロジェクトレビューでさまざまな設計を見てきましたし、より永続的な形で書かれたさまざまな設計も見てきました。これらの設計の中には有用なパターンがありますが、それらを説明するのは簡単ではありません。例としてModel-View-Controllerを取り上げます。これはパターンとしてよく参照されますが、私はそれをパターンとして考えるのはあまり有用ではないと感じています。なぜなら、それには非常に多くの異なるアイデアが含まれているからです。異なる場所でMVCについて読んだ異なる人々は、そこから異なるアイデアを取り出し、それらを「MVC」として説明しています。これによって十分な混乱が生じない場合、さらにセマンティック拡散を通じて発展するMVCの誤解という影響も生まれます。
このエッセイでは、興味深いアーキテクチャをいくつか探求し、その最も興味深い特徴についての私の解釈を説明したいと思います。これが、私が説明するパターンを理解するための文脈を提供することを願っています。
ある程度、このエッセイは、UI設計のアイデアを長年にわたって複数のアーキテクチャを通してたどる、一種の知的な歴史として見ることができます。しかし、これについては注意が必要です。アーキテクチャを理解するのは容易ではありません。特に、その多くが変化し、消滅するときはそうです。アイデアの広がりをたどることはさらに困難です。なぜなら、人々は同じアーキテクチャから異なることを読み取るからです。特に、私が説明するアーキテクチャの徹底的な調査は行っていません。私がしたのは、設計に関する一般的な説明を参照することです。それらの説明が何かを欠いている場合、私はそれをまったく知りません。したがって、私の説明を権威あるものとして受け止めないでください。さらに、特に適切だと思わなかったものについては、省略したり、単純化したりしました。私の主な関心事は、これらの設計の歴史ではなく、基礎となるパターンであることを覚えておいてください。
(ここには例外が1つあります。それは、MVCを調べるためにSmalltalk-80を実行することができたということです。繰り返しますが、私の調査を徹底的なものとは言いませんが、それによって、一般的な説明では欠けていたことが明らかになりました。そのため、私がここで行う他のアーキテクチャの説明にはさらに慎重になります。これらのアーキテクチャのいずれかに精通していて、私が重要な何かを誤って理解したり、見落としたりしている場合は、ぜひ教えてください。また、この領域のより徹底的な調査は、学術研究の良い対象になると思います。)
フォームとコントロール
この探求を、シンプルでなじみのあるアーキテクチャから始めたいと思います。一般的な名前がないため、このエッセイでは「フォームとコントロール」と呼ぶことにします。これは、90年代のクライアントサーバー開発環境(Visual Basic、Delphi、Powerbuilderのようなツール)で推奨されていたアーキテクチャであるため、なじみのあるアーキテクチャです。これは引き続き一般的に使用されていますが、私のようなデザインオタクからはしばしば非難されています。
それを探求するために、また他のアーキテクチャについても同様に、共通の例を使用します。私が住んでいるニューイングランドでは、大気中のアイスクリーム微粒子の量を監視する政府プログラムがあります。濃度が低すぎると、アイスクリームの摂取量が足りないことを示し、経済と治安に深刻なリスクをもたらします。(私は、この種の本によくあるものと現実性が変わらない例を使うのが好きです。)
私たちのアイスクリームの健康状態を監視するために、政府はニューイングランド州全体に監視ステーションを設置しました。複雑な大気モデリングを使用して、各監視ステーションの目標を設定します。スタッフは時々評価に出かけ、さまざまなステーションに行って、実際のアイスクリーム微粒子の濃度を記録します。このUIでは、ステーションを選択し、日付と実際の値を入力できます。次に、システムは目標からの偏差を計算して表示します。目標を下回る10%以上の場合は赤で、目標を上回る5%以上の場合は緑で偏差を強調表示します。

図1:例として使用するUI。
この画面を見ると、それを組み立てる際に重要な区分があることがわかります。フォームはアプリケーション固有のものですが、汎用的なコントロールを使用しています。ほとんどのGUI環境には、アプリケーションで使用できる共通コントロールが多数付属しています。新しいコントロールを自分で構築することもでき、そうすることが良いアイデアであることもよくありますが、汎用的な再利用可能なコントロールと特定のフォームの間には依然として区別があります。特別に作成されたコントロールでさえ、複数のフォームで再利用できます。
フォームには、主に2つの責任があります。
- 画面レイアウト:画面上のコントロールの配置と、相互の階層構造を定義します。
- フォームロジック:コントロール自体に簡単にプログラムできない動作。
ほとんどのGUI開発環境では、開発者はグラフィカルエディターで画面レイアウトを定義できます。これにより、コントロールをフォーム内のスペースにドラッグアンドドロップできます。これにより、フォーム上のコントロールの見栄えの良いレイアウトを簡単に設定できます(ただし、常に最適な方法とは限りません。後で説明します)。
コントロールはデータを表示します。この場合は、読み取りに関するデータです。このデータはほぼ常に別の場所から取得されます。この場合は、ほとんどのクライアントサーバーツールが想定している環境であるSQLデータベースを使用すると想定しましょう。ほとんどの場合、3つのデータのコピーが関係します。
- データの1つのコピーは、データベース自体に存在します。このコピーはデータの永続的な記録であるため、私はこれをレコード状態と呼びます。レコード状態は通常、さまざまなメカニズムを介して複数の人に共有および表示されます。
- もう1つのコピーは、アプリケーション内のインメモリのレコードセット内に存在します。ほとんどのクライアントサーバー環境では、これを簡単に行うためのツールが提供されていました。このデータは、アプリケーションとデータベース間の特定のセッションにのみ関連していたため、私はこれをセッション状態と呼びます。基本的に、これは、ユーザーが保存またはコミットしてデータベースに書き戻すまで、ユーザーが操作する一時的なローカルバージョンのデータを提供します。その時点で、レコード状態とマージされます。ここでは、レコード状態とセッション状態の調整に関する問題については気にしません。[P of EAA]でさまざまな手法について詳しく説明しました。
- 最後のコピーは、GUIコンポーネント自体の中に存在します。これは厳密には、画面に表示されるデータであるため、私はこれを画面状態と呼びます。画面状態とセッション状態をどのように同期させるかが、UIにとって重要です。
画面状態とセッション状態を同期させておくことは重要なタスクです。これを容易にするのに役立つツールがデータバインディングでした。このアイデアは、コントロールデータまたは基礎となるレコードセットへの変更は、もう一方に即座に伝播されるというものでした。したがって、画面上で実際の読み取りを変更すると、テキストフィールドコントロールは、基礎となるレコードセットの正しい列を効果的に更新します。
一般的に、データバインディングは、コントロールへの変更、レコードセットの変更、コントロールの更新、レコードセットの更新...というサイクルを回避する必要があるため、複雑になります。使用法は、これらを回避するのに役立ちます。画面を開いたときにセッション状態から画面にロードし、その後、画面状態への変更がセッション状態に伝播されます。画面が表示された後、セッション状態が直接更新されることはまれです。結果として、データバインディングは完全に双方向ではない可能性があります。つまり、最初のアップロードに限定され、その後、コントロールからセッション状態に変更を伝播するだけです。
データバインディングは、クライアントサーバーアプリケーションの多くの機能を非常にうまく処理します。実際の値を変更すると、列が更新され、選択したステーションを変更すると、レコードセットで現在選択されている行が変更され、他のコントロールが更新されます。
この動作の多くは、一般的なニーズを調べ、それらを簡単に満たすことができるフレームワークビルダーによって組み込まれています。特に、これはコントロールの値を設定することによって行われます。通常、プロパティと呼ばれます。コントロールは、単純なプロパティエディターで列名を設定することにより、レコードセットの特定の列にバインドされます。
適切な種類のパラメーター化を備えたデータバインディングを使用すると、長い道のりを進むことができます。ただし、それだけではすべてを解決できません。パラメーター化オプションに適合しないロジックがほとんど常に存在します。この場合、偏差の計算は、この組み込みの動作に適合しないものの例です。これはアプリケーション固有であるため、通常はフォーム内に存在します。
これが機能するためには、実際のフィールドの値が変更されたときにフォームに通知する必要があり、そのため、汎用的なテキストフィールドがフォーム上で特定の動作を呼び出す必要があります。これは、クラスライブラリを取得して、それを呼び出すことによって使用するよりも少し複雑です。制御の反転が関係するためです。
この種のものを機能させるにはさまざまな方法があります。クライアント/サーバーツールキットで一般的なのはイベントの概念でした。各コントロールには、発生させる可能性のあるイベントのリストがありました。外部オブジェクトは、コントロールに対して特定のイベントに関心があることを伝えることができました。その場合、コントロールはイベントが発生したときに外部オブジェクトを呼び出します。これは本質的に、フォームがコントロールを監視するObserverパターンの言い換えにすぎません。フレームワークは通常、フォームの開発者がイベントが発生したときに呼び出されるサブルーチンにコードを記述できるメカニズムを提供していました。イベントとルーチン間のリンクがどのように作成されたかはプラットフォームによって異なり、この議論では重要ではありません。重要なのは、それを実現する何らかのメカニズムが存在したということです。
フォーム内のルーチンが制御を取得すると、必要なことは何でも実行できます。特定の動作を実行し、必要に応じてコントロールを変更できます。これらの変更は、データバインディングを利用してセッション状態に伝播されます。
データバインディングが常に存在するわけではないため、これも必要です。ウィンドウコントロールには大きな市場がありますが、そのすべてがデータバインディングを行うわけではありません。データバインディングが存在しない場合、同期を実行するのはフォームの役割です。これは、最初にレコードセットからウィジェットにデータを引き出し、保存ボタンが押されたときに変更されたデータをレコードセットにコピーすることで機能します。
データバインディングが存在すると仮定して、実際の値の編集を見てみましょう。フォームオブジェクトは、ジェネリックコントロールへの直接参照を保持しています。画面上の各コントロールに1つずつありますが、ここでは実際のフィールド、差異フィールド、ターゲットフィールドのみに関心があります。

図2:フォームとコントロールのクラス図
テキストフィールドは、テキストが変更された場合のイベントを宣言します。フォームが初期化中に画面を組み立てるとき、自身をそのイベントにサブスクライブし、自身上のメソッド(ここではactual_textChanged
)にバインドします。

図3:フォームとコントロールを使用してジャンルを変更するシーケンス図。
ユーザーが実際の値を変更すると、テキストフィールドコントロールはそのイベントを発生させ、フレームワークのバインディングの魔法を通してactual_textChanged
が実行されます。このメソッドは、実際のテキストフィールドとターゲットテキストフィールドからテキストを取得し、減算を行い、差異フィールドに値を入力します。また、値を表示する色を判断し、テキストの色を適切に調整します。
アーキテクチャをいくつかの簡単な言葉で要約できます。
- 開発者は、ジェネリックコントロールを使用するアプリケーション固有のフォームを作成します。
- フォームは、コントロールのレイアウトを記述します。
- フォームはコントロールを監視し、コントロールによって発生する興味深いイベントに対応するハンドラーメソッドを持っています。
- 単純なデータ編集は、データバインディングを通じて処理されます。
- 複雑な変更は、フォームのイベント処理メソッドで行われます。
Model View Controller
UI開発で最も広く引用されているパターンはおそらくModel View Controller(MVC)ですが、最も誤って引用されているパターンでもあります。MVCとして説明されているものが、実際にはまったく似ていないというのを何度も見てきました。率直に言って、この理由の多くは、古典的なMVCの一部が最近のリッチクライアントには実際には意味をなさないためです。しかし、当面は、その起源を見てみましょう。
MVCを見る際に重要なことは、これが、規模に関係なく本格的なUI作業を行う最初の試みの1つであったことを覚えておくことです。グラフィカルユーザーインターフェイスは、70年代にはそれほど一般的ではありませんでした。私が今説明したフォームとコントロールのモデルはMVCの後に登場しました。まず、それがより単純であるため、最初に説明しました(常に良い方法ではありませんが)。繰り返しますが、評価例を使用してSmalltalk 80のMVCについて説明しますが、これを実行するためにSmalltalk 80の実際の詳細についていくつかの自由な解釈をしていることを認識してください。たとえば、それはモノクロシステムでした。
MVCの中核、そして後のフレームワークに最も影響を与えたアイデアは、私が分離されたプレゼンテーションと呼ぶものです。分離されたプレゼンテーションの背後にある考え方は、現実世界に対する私たちの認識をモデル化するドメインオブジェクトと、画面に表示されるGUI要素であるプレゼンテーションオブジェクトを明確に分割することです。ドメインオブジェクトは完全に自己完結型であり、プレゼンテーションを参照せずに動作する必要があります。また、複数のプレゼンテーションを同時にサポートできる必要もあります。このアプローチはUnixカルチャーの重要な一部でもあり、現在も多くのアプリケーションをグラフィカルインターフェイスとコマンドラインインターフェイスの両方で操作できるようにしています。
MVCでは、ドメイン要素はモデルと呼ばれます。モデルオブジェクトはUIをまったく認識していません。評価UIの例について説明を始めるにあたり、モデルを読み取り値として、それに興味深いすべてのデータのフィールドを設定します。(後でわかるように、リストボックスの存在により、モデルとは何かというこの問題はかなり複雑になりますが、しばらくの間はそのリストボックスを無視します。)
MVCでは、フォームとコントロールにあったレコードセットの概念ではなく、通常のオブジェクトのドメインモデルを想定しています。これは設計の背後にある一般的な想定を反映しています。フォームとコントロールは、ほとんどの人がリレーショナルデータベースからデータを簡単に操作したいと考えていると想定していました。MVCは、通常のSmalltalkオブジェクトを操作していると想定しています。
MVCのプレゼンテーション部分は、残りの2つの要素、ビューとコントローラーで構成されています。コントローラーの役割は、ユーザーの入力を受け取り、それで何をするかを判断することです。
この時点で、ビューとコントローラーは1つだけではなく、画面の各要素、各コントロール、および画面全体にビューとコントローラーのペアがあることを強調する必要があります。したがって、ユーザーの入力への反応の最初の部分は、編集されたものが誰であるかを確認するために、さまざまなコントローラーが連携することです。この場合、それは実際のテキストフィールドなので、そのテキストフィールドコントローラーは次に何が起こるかを処理します。

図4:モデル、ビュー、コントローラー間の本質的な依存関係。(私はこれを本質的と呼んでいますが、実際にはビューとコントローラーは互いに直接リンクしていますが、開発者はこの事実をほとんど使用しません。)
後の環境と同様に、Smalltalkは、再利用できる汎用UIコンポーネントが必要であることに気づきました。この場合、コンポーネントはビューとコントローラーのペアになります。両方とも汎用クラスであったため、アプリケーション固有の動作に接続する必要がありました。画面全体を表し、下位レベルのコントロールのレイアウトを定義する評価ビューがあり、その意味ではフォームとコントロールのフォームに似ています。ただし、フォームとは異なり、MVCには下位レベルのコンポーネントに対する評価コントローラーのイベントハンドラーはありません。

図5:アイスクリームモニタディスプレイのMVCバージョンのクラス
テキストフィールドの構成は、モデル(読み取り値)へのリンクを提供し、テキストが変更されたときに呼び出すメソッドを伝えることによって行われます。これは、画面が初期化されるときに「#actual:」に設定されます(先頭の「#」は、Smalltalkでのシンボルまたはインターンされた文字列を示します)。テキストフィールドコントローラーは、そのメソッドを読み取り値に対してリフレクティブに呼び出して変更を加えます。これは本質的に、データバインディングで発生するメカニズムと同じであり、コントロールは基になるオブジェクト(行)にリンクされ、操作するメソッド(列)が伝えられます。

図6:MVCの実際の値を変更します。
したがって、低レベルのウィジェットを監視する全体的なオブジェクトはなく、代わりに低レベルのウィジェットがモデルを監視し、モデル自体がフォームによって行われる多くの決定を処理します。この場合、差異を判断するとなると、読み取り値オブジェクト自体がそれを行う自然な場所です。
ObserverはMVCで発生します。実際、それはMVCに帰せられるアイデアの1つです。この場合、すべてのビューとコントローラーがモデルを監視します。モデルが変更されると、ビューは反応します。この場合、実際のテキストフィールドビューは、読み取り値オブジェクトが変更されたことが通知され、そのテキストフィールドのアスペクトとして定義されたメソッド(この場合は#actual)を呼び出し、その値を結果に設定します。(色は同様のことを行いますが、これについては後で触れることにします。)
テキストフィールドコントローラーはビュー自体に値を設定せず、モデルを更新し、オブザーバーメカニズムが更新を処理するようにしたことに気づくでしょう。これは、フォームがコントロールを更新し、データバインディングに依存して基になるレコードセットを更新するフォームとコントロールのアプローチとはまったく異なります。これらの2つのスタイルを、パターンとして説明します。フロー同期と仲介同期です。これらの2つのパターンは、画面状態とセッション状態間の同期のトリガーを処理する代替方法を説明します。フォームとコントロールは、更新する必要のあるさまざまなコントロールを直接操作するアプリケーションのフローを通じてそれを行います。MVCは、モデルに対する更新を行い、そのモデルを監視しているビューを更新するためにオブザーバーの関係に依存することでそれを行います。
フロー同期は、データバインディングが存在しない場合にさらに顕著になります。アプリケーション自体が同期を行う必要がある場合、通常、画面を開いたり、保存ボタンを押したりするなど、アプリケーションフローの重要なポイントで行われます。
仲介同期の結果の1つは、コントローラーが、ユーザーが特定のウィジェットを操作したときに、他のどのウィジェットを変更する必要があるかについて非常に無知であることです。フォームは状況を監視し、変更時に画面全体の状態に一貫性があることを確認する必要があり、複雑な画面ではかなり複雑になる可能性がありますが、仲介同期のコントローラーは、これらすべてを無視できます。
この役立つ無知は、同じモデルオブジェクトを表示する複数の画面が開いている場合に特に便利になります。古典的なMVCの例は、スプレッドシートのようなデータの画面と、別々のウィンドウにそのデータのいくつかの異なるグラフがありました。スプレッドシートウィンドウは、他のどのウィンドウが開いているかを認識する必要はありませんでした。モデルを変更しただけで、仲介同期が残りを処理しました。フロー同期を使用すると、どの他のウィンドウが開いているかを把握して、それらに更新を指示する必要がありました。
仲介同期は優れていますが、欠点もあります。仲介同期の問題は、オブザーバーパターン自体の中心的な問題です。つまり、コードを読むことによって何が起こっているかを知ることができません。Smalltalk 80の一部の画面がどのように機能するかを理解しようとしたときに、このことを非常に強く思い出しました。コードを読むことで何とかできたのですが、オブザーバーメカニズムが作動すると、何が起こっているのかを確認する唯一の方法はデバッガーとトレースステートメントでした。オブザーバーの動作は暗黙的な動作であるため、理解とデバッグが困難です。
同期に対するさまざまなアプローチは、シーケンス図を見ると特に顕著ですが、最も重要で影響力のある違いは、MVCが分離されたプレゼンテーションを使用していることです。実際値と目標値の間の差異を計算することはドメインの振る舞いであり、UIとは関係ありません。その結果、分離されたプレゼンテーションに従うと、これをシステムのドメイン層に配置する必要があると言えます。これはまさにreadingオブジェクトが表しているものです。readingオブジェクトを見ると、差異機能はユーザーインターフェースの概念なしに完全に意味をなします。
ただし、ここでいくつかの複雑な点を見ていきましょう。私はMVC理論の邪魔になる厄介な点を2つほど飛ばしました。最初の問題点は、差異の色を設定することに対処することです。値を表示する色はドメインの一部ではないため、ドメインオブジェクトには実際には適合しません。これに対処する最初のステップは、ロジックの一部がドメインロジックであることを理解することです。ここで私たちは、差異について質的なステートメントを作成しており、それを「良好」(5%以上超過)、「不良」(10%以上不足)、「正常」(残りの部分)と表現できます。この評価を下すことは確かにドメイン言語であり、それを色にマッピングして差異フィールドを変更することはビューロジックです。問題は、このビューロジックをどこに配置するかです。それは標準のテキストフィールドの一部ではありません。
このような問題は、初期のスモールトーカーたちが直面し、いくつかの解決策を考え出しました。私が上で示した解決策は、汚い解決策です。物事を機能させるためにドメインの純粋さをいくらか妥協しています。私は時々不純な行為をすることを認めますが、それを習慣にしないように努めています。
Forms and Controlsが行っているように、ほぼ同じことができます。評価画面ビューに差異フィールドビューを監視させ、差異フィールドが変更されたときに評価画面が反応して差異フィールドのテキストカラーを設定することができます。ここでの問題点としては、オブザーバーメカニズムのさらなる使用(使用するほど指数関数的に複雑になります)や、さまざまなビュー間の余分な結合などがあります。
私が好む方法は、新しいタイプのUIコントロールを構築することです。本質的に必要なのは、ドメインに質的な値を要求し、それを内部の値と色のテーブルと比較し、それに応じてフォントカラーを設定するUIコントロールです。テーブルとドメインオブジェクトに要求するメッセージの両方が、監視するフィールドのアスペクトを設定するのと同じように、評価ビューが自身を組み立てるときに設定されます。このアプローチは、テキストフィールドを簡単にサブクラス化して追加の動作を追加できる場合に非常にうまく機能する可能性があります。これは明らかに、コンポーネントがサブクラス化を可能にするように設計されているかどうかに依存します。スモールトークでは非常に簡単でしたが、他の環境ではより困難になる可能性があります。

図7:色を決定するように構成できるテキストフィールドの特別なサブクラスの使用。
最後の方法は、画面を中心に構成された、ウィジェットに依存しない新しい種類のモデルオブジェクトを作成することです。それは画面のモデルになります。readingオブジェクトと同じメソッドはreadingに委任されますが、テキストカラーなど、UIにのみ関連する動作をサポートするメソッドが追加されます。

図8:ビューロジックを処理するために中間プレゼンテーションモデルを使用。
この最後のオプションは、多くのケースでうまく機能し、見てわかるように、スモールトーカーが従う一般的なルートになりました。私はこれをプレゼンテーションモデルと呼んでいます。これは、プレゼンテーション層のために実際に設計され、その一部であるモデルであるためです。
プレゼンテーションモデルは、別のプレゼンテーションロジックの問題であるプレゼンテーション状態にもうまく機能します。基本的なMVCの概念では、ビューのすべての状態はモデルの状態から派生できると想定しています。この場合、リストボックスでどのステーションが選択されているかをどうやって把握するのでしょうか? プレゼンテーションモデルは、この種の状態を配置する場所を提供することでこれを解決してくれます。データが変更された場合にのみ有効になる保存ボタンがある場合にも、同様の問題が発生します。これはモデル自体ではなく、モデルとのインタラクションに関する状態です。
では、ここでMVCに関するいくつかの要点をお伝えしましょう。
- プレゼンテーション(ビューとコントローラー)とドメイン(モデル)を強力に分離する - 分離されたプレゼンテーション。
- GUIウィジェットを、コントローラー(ユーザーの刺激に反応するため)とビュー(モデルの状態を表示するため)に分割します。コントローラーとビューは(ほとんどの場合)直接通信するのではなく、モデルを介して通信する必要があります。
- 複数のウィジェットが直接通信することなく更新できるように、ビュー(およびコントローラー)にモデルを監視させる - オブザーバー同期。
VisualWorksアプリケーションモデル
上で説明したように、Smalltalk 80のMVCは非常に影響力があり、優れた機能もいくつかありましたが、欠点もいくつかありました。80年代と90年代にスモールトークが発展するにつれて、これは古典的なMVCモデルのいくつかの重要なバリエーションにつながりました。実際、ビュー/コントローラーの分離がMVCの本質的な部分であると考えるなら、MVCは消滅したと言えるかもしれません。名前が示唆するように。
MVCから明らかにうまく機能したものは、分離されたプレゼンテーションとオブザーバー同期でした。したがって、これらはスモールトークが発展するにつれて残りました。実際、多くの人にとって、これらはMVCの重要な要素でした。
スモールトークもこの時期に分裂しました。スモールトークの基本的なアイデア(最小限の言語定義を含む)は同じままでしたが、さまざまなライブラリを持つ複数のスモールトークが開発されました。UIの観点からは、いくつかのライブラリがネイティブウィジェット、つまりForms and Controlsスタイルで使用されるコントロールを使用し始めたため、これは重要になりました。
スモールトークはもともとゼロックスPARCラボによって開発され、彼らはスモールトークを販売および開発するために別の会社であるParcPlaceを設立しました。ParcPlaceスモールトークはVisualWorksと呼ばれ、クロスプラットフォームシステムであることを強調しました。Javaよりもずっと前に、Windowsで作成されたスモールトークプログラムをSolarisですぐに実行できました。その結果、VisualWorksはネイティブウィジェットを使用せず、GUIを完全にスモールトーク内に維持しました。
MVCに関する私の議論では、MVCのいくつかの問題、特にビューロジックとビューステートをどのように処理するかについて終えました。VisualWorksは、プレゼンテーションモデルに向かう構造であるアプリケーションモデルと呼ばれる構造を考案することにより、これを処理するためにフレームワークを改良しました。プレゼンテーションモデルのようなものを使用するという考えは、VisualWorksにとって新しいものではありませんでした。オリジナルのSmalltalk 80コードブラウザは非常に似ていましたが、VisualWorksアプリケーションモデルはそれをフレームワークに完全に組み込みました。
この種のスモールトークの重要な要素は、プロパティをオブジェクトに変えるというアイデアでした。プロパティを持つオブジェクトの通常の概念では、Personオブジェクトには名前とアドレスのプロパティがあると考えています。これらのプロパティはフィールドである可能性がありますが、他のものである可能性もあります。プロパティにアクセスするための標準的な規則が通常あります。Javaでは、temp = aPerson.getName()
とaPerson.setName("martin")
が表示され、C#ではtemp = aPerson.name
とaPerson.name = "martin"
が表示されます。
プロパティオブジェクトは、実際の値をラップするオブジェクトをプロパティが返すことによって、これを変更します。したがって、VisualWorksで名前を要求すると、ラッピングオブジェクトが返されます。次に、ラッピングオブジェクトに値を要求することによって、実際の値を取得します。したがって、人の名前にアクセスするには、temp = aPerson name value
とaPerson name value: 'martin'
を使用します。
プロパティオブジェクトにより、ウィジェットとモデル間のマッピングが少し簡単になります。対応するプロパティを取得するためにウィジェットが送信するメッセージをウィジェットに伝えるだけで済み、ウィジェットは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:実行中の例でのビジュアルワークスアプリケーションモデルのクラス図
アプリケーションモデルと従来のMVCの使用の主な違いは、ドメインモデルクラス(Reader)とウィジェットの間に中間クラスができたことです。これがアプリケーションモデルクラスです。ウィジェットはドメインオブジェクトに直接アクセスしません。それらのモデルはアプリケーションモデルです。ウィジェットは依然としてビューとコントローラーに分割されますが、新しいウィジェットを構築しない限り、その区別は重要ではありません。
UIを組み立てるときは、UIペインターで行いますが、そのペインターで各ウィジェットのアスペクトを設定します。アスペクトは、プロパティオブジェクトを返すアプリケーションモデルのメソッドに対応します。

図10:実際の値を更新すると差異テキストが更新される仕組みを示すシーケンス図。
図10は、基本的な更新シーケンスがどのように機能するかを示しています。テキストフィールドで値を変更すると、そのフィールドはアプリケーションモデル内のプロパティオブジェクトの値を更新します。その更新は、基になるドメインオブジェクトにまで及び、その実際の値を更新します。
この時点で、オブザーバーの関係が開始されます。実際の値を更新すると、readingが変更されたことを示すように設定する必要があります。これを行うには、実際の修飾子に、readingオブジェクトが変更されたことを示す呼び出しを配置します。特に、差異アスペクトが変更されたことを示します。差異のアスペクトアダプターを設定するときに、リーダーを監視するように簡単に指示できるため、更新メッセージを取得し、それをテキストフィールドに転送します。テキストフィールドは、アスペクトアダプターを介して再度新しい値の取得を開始します。
このようにアプリケーションモデルとプロパティオブジェクトを使用すると、多くのコードを記述せずに更新を接続できます。また、細かい粒度の同期(私は良いことだとは思いません)もサポートしています。
アプリケーションモデルを使用すると、UI に特有の振る舞いと状態を、実際のドメインロジックから分離できます。したがって、以前に述べた問題の 1 つである、リストで現在選択されている項目を保持することは、ドメインモデルのリストをラップし、現在選択されている項目も格納する特定の種類のアスペクトアダプターを使用することで解決できます。
ただし、このすべての限界は、より複雑な動作には、特別なウィジェットとプロパティオブジェクトを構築する必要があるということです。例として、提供されたオブジェクトのセットは、分散のテキストの色を分散の度合いに関連付ける方法を提供していません。アプリケーションモデルとドメインモデルを分離することで、意思決定を適切に行うことができますが、アスペクトアダプターを監視するウィジェットを使用するには、いくつかの新しいクラスを作成する必要があります。多くの場合、これは手間がかかりすぎると考えられていたため、図 11 に示すように、アプリケーションモデルがウィジェットに直接アクセスできるようにすることで、この種のことをより簡単にすることができました。

図 11: アプリケーションモデルがウィジェットを直接操作して色を更新する。
このようにウィジェットを直接更新することは、プレゼンテーションモデルの一部ではありません。これが、VisualWorks アプリケーションモデルが真の プレゼンテーションモデルではない理由です。ウィジェットを直接操作する必要性は、多くの人にやや汚い回避策と見なされており、Model-View-Presenter アプローチの開発に役立ちました。
それでは、アプリケーションモデルに関する簡単な説明をします。
- 分離されたプレゼンテーションとオブザーバー同期の使用において、MVC に従った。
- プレゼンテーションロジックと状態の場所として中間アプリケーションモデルを導入 - プレゼンテーションモデルの部分的な開発。
- ウィジェットはドメインオブジェクトを直接監視するのではなく、アプリケーションモデルを監視する。
- さまざまなレイヤーを接続し、オブザーバーを使用したきめ細かい同期をサポートするために、プロパティオブジェクトを広範囲に使用した。
- アプリケーションモデルがウィジェットを操作することはデフォルトの動作ではなかったが、複雑なケースでは一般的に行われていた。
Model-View-Presenter (MVP)
MVP は、1990 年代に IBM で最初に登場し、Taligent でより目立ったアーキテクチャです。最も一般的にPotelの論文で言及されています。この考えは、Dolphin Smalltalkの開発者によってさらに普及し、説明されました。後で見るように、2 つの説明は完全に一致しませんが、その根底にある基本的な考え方は人気を博しています。
MVP に取り組む上で、私は UI の考え方の 2 つの大きな流れの間にある重要なミスマッチについて考えることが役立つと思っています。一方には、UI デザインへの主流のアプローチであったフォームとコントローラーのアーキテクチャがあり、他方には MVC とその派生物があります。フォームとコントロールモデルは、理解しやすく、再利用可能なウィジェットとアプリケーション固有のコードを適切に分離する設計を提供します。これに欠けており、MVC が非常に強く持っているのは、分離されたプレゼンテーションであり、実際にはドメインモデルを使用したプログラミングのコンテキストです。私は MVP を、これらの流れを統合し、それぞれの最良の部分を取り入れようとするステップと見ています。
Potelの最初の要素は、ビューをウィジェットの構造、つまりフォームとコントロールモデルのコントロールに対応するウィジェットとして扱い、ビュー/コントローラーの分離を削除することです。MVP のビューは、これらのウィジェットの構造です。ウィジェットがユーザーインタラクションにどのように反応するかを記述する動作は含まれていません。
ユーザーアクションへのアクティブな反応は、別のプレゼンターオブジェクトに存在します。ユーザージェスチャの基本的なハンドラーは、ウィジェットにまだ存在しますが、これらのハンドラーはプレゼンターに制御を渡すだけです。
次に、プレゼンターはイベントにどのように反応するかを決定します。Potel は、コマンドと選択のシステムによって、主にモデルに対するアクションの観点からこのインタラクションについて議論しています。ここで強調すべき有用な点は、モデルへのすべての編集をコマンドにパッケージ化するというアプローチです。これは、元に戻す/やり直しの動作を提供するための優れた基盤となります。
プレゼンターがモデルを更新すると、MVC が使用するのと同じ オブザーバー同期アプローチを通じてビューが更新されます。
Dolphinの説明も同様です。ここでも、主な類似点はプレゼンターの存在です。Dolphin の説明では、コマンドと選択を介してモデルに対して動作するプレゼンターの構造はありません。プレゼンターがビューを直接操作するという明確な議論もあります。Potel は、プレゼンターがこれを行うべきかどうかについては言及していませんが、Dolphin にとって、この機能は、バリエーションフィールドのテキストに色を付けるのが面倒になったアプリケーションモデルの欠陥を克服するために不可欠でした。
MVP について考える際のバリエーションの 1 つは、プレゼンターがビューのウィジェットをどの程度制御するかということです。一方では、すべてのビューロジックがビューに残され、プレゼンターがモデルのレンダリング方法の決定に関与しないケースがあります。このスタイルは、Potelによって暗示されています。Bower と McGlashan の背後にある方向性は、私が監督コントローラーと呼んでいるものであり、ビューは宣言的に記述できるビューロジックの多くを処理し、プレゼンターはより複雑なケースを処理するために介入します。
プレゼンターにすべてのウィジェットの操作を行わせるようにすることもできます。このスタイルは、私がパッシブビューと呼んでおり、MVP の元の説明の一部ではありませんが、人々がテスト可能性の問題を探求するにつれて開発されました。このスタイルについては後で説明しますが、このスタイルは MVP のフレーバーの 1 つです。
以前に説明した内容と MVP を対比する前に、MVP に関する両方の論文もこれを行っていることについても触れておく必要があります。ただし、私が持っている解釈とまったく同じではありません。Potel は、MVC コントローラーが全体的なコーディネーターであったことを示唆していますが、それは私がそれらをどのように見ているかではありません。Dolphin は MVC の問題について多く語っていますが、MVC とは、私が説明した古典的な MVC ではなく、VisualWorks アプリケーションモデルのデザインを意味しています。(私は彼らを責めません。古典的な MVC に関する情報を入手しようとするのは、今では簡単ではなく、当時もそうでした。)
それでは、いくつかの対比を行います。
- フォームとコントロール: MVP にはモデルがあり、プレゼンターはこのモデルを操作することが期待され、オブザーバー同期を使用してビューを更新します。ウィジェットへの直接アクセスは許可されていますが、これは最初の選択肢ではなく、モデルを使用することに加えて行う必要があります。
- MVC: MVP は監督コントローラーを使用してモデルを操作します。ウィジェットはユーザージェスチャを監督コントローラーに渡します。ウィジェットは、ビューとコントローラーに分離されていません。プレゼンターをコントローラーのように考えることができますが、ユーザージェスチャの初期処理はありません。ただし、プレゼンターは通常、ウィジェットレベルではなくフォームレベルにあることも重要です。これは、おそらくさらに大きな違いです。
- アプリケーションモデル: ビューは、アプリケーションモデルと同様に、イベントをプレゼンターに渡します。ただし、ビューはドメインモデルから直接自身を更新する可能性があり、プレゼンターはプレゼンテーションモデルとして機能しません。さらに、プレゼンターは、オブザーバー同期に適合しない動作のためにウィジェットに直接アクセスすることを歓迎します。
MVP プレゼンターと MVC コントローラーの間には明らかな類似点があり、プレゼンターは MVC コントローラーの緩やかな形式です。その結果、多くの設計は MVP スタイルに従いますが、プレゼンターの同義語として「コントローラー」を使用します。ユーザー入力を処理する場合、一般的にコントローラーを使用するという合理的な議論があります。

図 12: MVP での実際の読み取り更新のシーケンス図。
アイスクリームモニターの MVP (監督コントローラー) バージョンを見てみましょう (図 12)。フォームとコントロールバージョンとほぼ同じように始まります。実際のテキストフィールドは、テキストが変更されたときにイベントを発生させ、プレゼンターはこのイベントをリッスンしてフィールドの新しい値を取得します。この時点で、プレゼンターは読み取りドメインオブジェクトを更新します。読み取りドメインオブジェクトをバリアンスフィールドが監視し、テキストを更新します。最後の部分は、バリアンスフィールドの色の設定であり、プレゼンターによって行われます。プレゼンターは、読み取りからカテゴリを取得し、バリアンスフィールドの色を更新します。
これが MVP の簡単な説明です。
Humble View
ここ数年で、自己テストコードを作成する強い流行がありました。ファッションセンスについて最後に尋ねる人であるにもかかわらず、これは私が徹底的に浸っている動きです。私の同僚の多くは、xUnit フレームワーク、自動回帰テスト、テスト駆動開発、継続的インテグレーション、および同様のバズワードの大ファンです。
人々が自己テストコードについて話すとき、ユーザーインターフェイスはすぐに問題として頭を上げます。多くの人は、GUI のテストが困難と不可能の間にあると感じています。これは主に、UI が UI 環境全体に密接に結合しており、分解して個別にテストすることが難しいためです。
場合によっては、このテストの困難さが誇張されることがあります。テストコードでウィジェットを作成して操作することで、驚くほどうまくいくことがよくあります。しかし、それが不可能な場合、重要なインタラクションを見逃したり、スレッドの問題が発生したり、テストの実行が遅すぎたりする場合があります。
その結果、テストが難しいオブジェクトの振る舞いを最小限にするようなUIを設計する動きが着実に進んでいます。Michael Feathersは、The Humble Dialog Boxでこのアプローチを簡潔にまとめています。Gerard Meszarosは、この概念をHumble Objectというアイデアに一般化しました。テストが難しいオブジェクトは、振る舞いを最小限にすべきであるということです。そうすることで、テストスイートに含めることができない場合でも、検出されない障害のリスクを最小限に抑えることができます。
The 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 ViewはPresentation Modelに存在する小さなリスクさえも排除します。ただし、その代償として、テスト実行中に画面を模倣するためにテストダブルが必要になります。これは、構築する必要がある追加の仕組みです。
Supervising Controllerでも同様のトレードオフが存在します。ビューに簡単なマッピングを行わせると、ある程度のリスクは発生しますが、(Presentation Modelと同様に)簡単なマッピングを宣言的に指定できるという利点があります。Supervising Controllerの場合、マッピングはPresentation Modelよりも小さくなる傾向があります。複雑な更新でさえPresentation Modelによって決定され、マッピングされる一方、Supervising Controllerは、マッピングを介さずに複雑なケースでウィジェットを操作するためです。
さらに読む
これらのアイデアをさらに発展させた最近の記事については、私のblikiをご覧ください。
謝辞
Vassili Bykov氏は、彼のSmalltalk-80バージョン2(1980年代初頭のもの)の実装であるHobbesのコピーを寛大にも提供してくれました。これは、最新のVisualWorksで実行されます。これにより、Model-View-Controllerのライブの例が得られ、その仕組みやデフォルトイメージでの使用方法に関する詳細な質問に答える上で非常に役立ちました。当時、多くの人々は仮想マシンを使用することは実用的ではないと考えていました。私たちが昔の自分たちが、Ubuntu上で実行されているVMware仮想マシンで実行されているWindows XP上のVisualWorks仮想マシンで実行されているVisualWorksで記述された仮想マシンでSmalltalk 80を実行しているのを見たら、どのように思ったでしょうか。
重要な改訂
2006年7月18日:開発Webサイトでの初公開。