この記事では、Flex アプリケーションのアーキテクチャー概要を扱います。以下の内容は、Flex アプリケーション構築の際に一般的に起こる、と思われる問題への対応例を紹介することが目的です。Flex アプリケーションを常に同じ形で構築することを推奨するものではありません。
クライアント側とサーバー側を含めたアプリ全体のアーキテクチャーについては別記事 (アプリ全体編) をご覧下さい。
はじめに
MVC
MVC は、もともと UI 設計のために考案されたアーキテクチャーで、基本となる考えは、Model を "プレゼンテーション" (View + Controller) から独立させることです。Model と "プレゼンテーション" の 2 つの要素の関係は、それぞれ、「Controller は ユーザーの入力に基づいて Model を変更する」、「View は Model を観察 (Observer パターン) することで、Model と同期を取る」、となります。
ここで Model が意味するものは、データと処理を内部に持ち、独立して振る舞うことのできるオブジェクトです。Model は複数の "プレゼンテーション" と同時に組み合わせることができます。また、スクリーン内には複数の "プレゼンテーション" の存在が想定されています。
さて、MVC は 30 年以上前に考案されたものです。それもあってか、リッチクライアントに適用するには少々用をなさない面があります。例えば、テキストフィールドにフォーカスが当たったらフィールドをハイライト表示にしよう、と思ったとき、MVC にはそのためのロジックとデータを持つ場所がありません。MVC の Model は "プレゼンテーション" に表示されるデータ項目を扱いますが、 "プレゼンテーション" の状態の管理は対象外だからです。
Presentation Model
この問題に対応するパターンの 1 つが Presentation Model です。Presentation Model パターンでは、"プレゼンテーション" 専用のモデル (Presentation Model) を用意し、そこに表示データに加えて、"プレゼンテーション" の状態、例えば、フィールドがフォーカスされているか、といった情報を持ちます。ユーザーが値を入力すると、まず Presentation Model の属性が更新され、それが Model (MVC の Model に相当する Model) に伝えられます。
このパターンでは、Model が更新されたら Presentation へ同期する役割を Presentation Model が担当する、という実装が一般的です。これは、MVC の基本的な発想 (Model のプレゼンテーションからの分離) の観点からは望ましくないと考えられます。
Supervising Controller
別の UI デザインパターンに Supervising Controller があります。これは、MVP (Model-View-Presenter) パターンの一種で、Presentation Model のように Model を 2 つ持つ代わりに、Controller を分離したものと考えることができます。Supervising Controller では、View に簡単な宣言レベルのロジック (簡易 Controller) を持ちますが、Model とのデータ同期処理や、View の操作は Presenter (上位 Controller) が担当します。
ユーザーの操作は View から Presenter に通知されます。Presenter が View から値を取得して Model を変更すると、それが View に反映される、というのが Supervising Controller の流れです。Model と View の関係は MVC に似ています。
Passive View
Passive View も、MVP の一種とされるパターンです。こちらは、Supervising Controller と違い、View にロジックを持たず、View への値の設定も Presenter が行います。そのため、このパターンでは Model は View と関連を持ちません。Flex 4 から導入された新しい Spark コンポーネントはこのアーキテクチャーの形をしています。これは、デザイナーが主に扱うもの (View) と、開発者が主に扱うもの (Presenter) を分けること、が目的の 1 つだったと想像されます。はからずも (?) Flash Catalyst と Flash Builder の関係も、この View と Presenter の関係を模したものになっています。
一般的な Flex アプリケーションの構造
それでは、Flex アプリケーションによく見られる構造を紹介したいと思います。できるだけ、一般的なデザインパターンを借りた表現を試みます。実際のアプリでは、M, V, C に加えて、サーバーとのデータ同期機能も必要なので、クライアント側のビジネス層として取り上げます。Flex アプリケーションは、開発者に特定のアーキテクチャーを強要することはありません。以下で紹介する形以外にも、任意の DI コンテナやアーキテクチャーフレームワークを採用することができます。
Flex アプリケーションで、コンポーネントを連携させる方法は 3 つあります。メソッド、イベント、データバインディングです。イベントは Observer パターンの実装として使われます。また、データバインディングは、イベントにデータ同期機能が加わったものと考えられます。
View 層
Flex の View は、ボタンやリストといった UI コンポーネントが並べられた MXML ファイルです。通常、Flex の UI は 複数の View (MXML ファイル) から構成され、それらの View は Application または WindowedApplication を最上位の View とする階層構造をなしています。つまり、Flex の UI のルートはアプリケーションオブジェクト、末端は Spark ないし Halo アーキテクチャーのコンポーネントという形です。
複数存在する View と、それらに対応する Controller あるいは Model の関係を祖結合として実現するには、まず、DI フレームワークの利用が考えられます。大規模な開発では、この方法が一般的でしょう。この場合は、DI フレームワークが用意する仕組みを使って、M-V-C 間をつなげることになります。
DI フレームワークを使用しない場合には、Observer のパターンを実現するため、Flash の DisplayList が持っているイベントフローの仕組みを使うのが一般的です。DilsplayList では、ユーザー入力によるイベントが親オブジェクトに伝えられ、最上位のオブジェクトまで伝搬します。MXML の階層構造はそのまま DisplayList として扱えるため、DisplayList の利用は最も手軽な手段だと考えられます。
その際、Model の変更を View に反映するにはデータバインディングを使います。データバインディングは、自動的に階層を下方向に伝搬しないので、親子間それぞれに、必要なデータバインディングの設定を行います。
MVC には、View 専用のロジックと Controller および Model との関係をどう扱うか、という選択があります。この問題に対しては、「はじめに」 でも触れたように、いくつかの良く知られたパターンが存在します。MVC ではなく MVP を選択する場合もあるでしょう。どのような構造を採用するにしろ、MXML ファイル内には表示に必要な宣言のみ記述し、ロジックは別途 AS ファイルに分けるのがベストプラクティスとされています。
Model 層
改めて書くまでもなく、Model を View や Controller から祖結合にする際にも、DI コンテナの利用は有効です。
DI コンテナを使用しない場合は、Model をシングルトンとして実装するという方法があります。実際に値を保持する複数のオブジェクトを管理するクラスを用意し、外部からはクラスのインターフェースを通じて必要なオブジェクトへの参照を獲得するという方法です。
Controller 層
Controller の主な仕事は、ユーザー入力に応じて Model を更新することです。「View のレイヤー」 で触れたように、ユーザー入力は View からイベントとして Controller に伝えられます。その際、Controller は処理の割り振りのみを行い、実際の処理は Command パターンにより実現するのが一般的です。Command を使うことで、Controller が肥大化したり、重複したコードが複数箇所にある、といった状態を避けることが可能になります。また、Command には、操作の "取り消し" の様な複雑な機能や、マクロ機能の実現がしやすいという利点もあります。ビジネス層との連携にも Command は使われます。
上の図で想定している流れは、Command が Model を更新 → View が Model と同期、というものです。その場合は、Command は View についての情報を必要としません。しかし、View に固有のロジック、例えば、サーバーからデータを受信完了したらダイアログボックスを開く、といった処理は、View への参照無しに行うことはできません。とはいえ、Command に View 層への直接参照を持たせると、せっかく Command として処理を隠蔽した意味が無くなってしまいます。
このような場合に使われるのが View Helper です。View Helper は View への参照と View 固有のロジックを持ち、Command からのイベントを待ちます。View Helper を使うことで、Command から特定の View に依存する処理を切り離すことができます。このとき、View Helper は、ロジックを View から切り離す役割も果たしています。
クライアント側ビジネス層
クライアント側ビジネス層の主な役割は、Controller から起動された Command に従ってサーバーと通信を行い、その結果を Model に反映することです。その際、Command にサーバーアクセス手段が記述されていたら、サーバー側の実装が変わるごとに、関連する全ての Command を修正しなければなりません。そこで、クライアント側からサーバー側の実装への依存を減らすために Business Delegate パターンが使われます。これにより、例えば、開発から運用段階にかけてサーバーの URL やアクセス手順が変わったり、通信方式が HTTPService から RemoteObject に変更されたり、といった場合でも、クライアント側のビジネス層で吸収できるようになります。
Flex の RPC サービスを利用した場合、サーバーからの結果を Responder オブジェクトとして受け取ることができます。このとき Responder は Controller 経由、または直接 Command を呼び出して Model を更新します。
おわりに
ここまで、Flex アプリケーションに一般的と思われる問題点と、それらに対応するパターンを紹介しました。この記事のポイントは、リッチクライアント開発時に、コードの可読性、再利用性、そして拡張性を高めるため、クラスの役割を明確に分けるのに役立つコンセプトを紹介することです。実際の開発で、これら全てのパターンを使う必要はありませんし、全く違う構造が適している場合もあるかもしれません。
本記事では扱いませんでしたが、興味を持った方のために、以下が主な DI フレームワークです。