JavaFXとMVC¶
はじめに¶
GUIの設計において、クラスをどう分割するか、分割したクラスの間の協調をどうするかが大きな課題です。
GUIにおける設計の原則は、"Model-View Separation"(モデルとビューの分離)です。
「モデルとビューの分離」原則を実現するための設計手法として幾つかのパターンが考案されています。
注)ここでいうモデルは、MVCのMではなく、ドメインモデルやビジネスロジックなどです。
- MVC(Model View Controller)とその変種
- PM(Presentation Model)
- MVP(Model View Presenter)
- MVVM(Model View ViewModel)
- PAC(Presentation Abstraction Controller)
- クラス間の協調の仕方
- Observer
- Broker
- Mediator
- Publish subscribe
MVC1は、オブジェクト指向プログラミング初期の頃(Smalltalk)に導入された設計の考え方で、ユーザーインタフェースを3つの責務に分割し、それぞれの協調の仕方をパターン化したものです。MVCの変種(改良型)として、PM、MVP、MVVMがあると考えます(異論あるかもしれませんが)。
PACは、POSA本2で紹介された、複雑なGUIをPACエージェントというコンポーネントに分割し、PACエージェント内はPresentation Abstraction Modelの3つに分割し(ここはMVCの亜種に見える)、PACエージェント同士を階層的に構築する設計のパターンです。
クラス間の協調の仕方は、モデルとビューとの間、PACエージェント内のクラス間、PACエージェント間などに使われます。
1 Webアプリケーション開発でのMVCとは違う
2 書籍、正式名称は"Pattern Oriented Software Architecture"で、邦訳本あり
「ソフトウェアアーキテクチャ―ソフトウェア開発のためのパターン体系 」(近代科学社、2000年)
JavaFXアプリケーションの構造とMVC¶
JavaFXアプリケーションには、全部Javaコードで記述する方法、FXMLを使ってレイアウトを定義する方法、CSSを使って見栄えを定義する方法といくつかの組み合わせがあります。ここでは、レイアウトをFXMLで定義し、見栄えをCSSで定義するフル構成を想定します。
JavaFXアプリケーションの構成¶
JavaFXのドキュメントで解説されているプログラムやNetBeansで生成したアプリケーションプログラムは次の構造になっています。
- ApplicationメインクラスはFXMLファイルをロードして初期化します。FXMLファイルに、FXMLに対応するコントローラークラスを記述しておくとロード時にコントローラークラスのインスタンスが生成され対応付けされます。そのため、上図にはメインクラスからコントローラークラスへの関連付けをしていません。
- コントローラーでは、FXMLで定義したコンポーネント部品への参照をDI(依存関係のインジェクション)で保持します。FXMLで定義するViewへの参照は持たないので、上図にはコントローラークラスからViewへの関連付けをしていません。
このJavaFXアプリケーションの基本構造にはアプリケーションのデータ(ドメインモデルとかビジネスロジックとかいわれる)を置く場所が明確に定められていません。
通常、先に述べた「モデルとビューの分離」原則にもとづき、ドメインデータはビュー(GUI)に依存しないように設けます。
そこで、クラス間の関連が片方向となる次の構造を取ることが多いです。
上図では単純にコントローラークラスから直接ドメインモデルを参照する構造としています。
実際にGUIを作成する場合、モデルの状態変更をビューに自動反映させる(オブザーバーパターン等の)構造や、GUIに閉じた状態を持つことが多いので、GUI特有の構造や状態とドメインモデルをさらに分けるなど、ドメインモデルの入れ方をどのようにするかが、JavaFXを使ったアプリケーションプログラムの1つのこつになるのではないかと考えています。
FXMLに対応するコントローラにドメインモデルへの参照を持たせる¶
FXMLに対応するControllerは、FXMLファイルをロードしたときにJavaFXライブラリ側でインスタンス化されます。
そのため、アプリケーション側でFXMLに対応するコントローラをインスタンス化してドメインモデルへの参照を持たせるには工夫が必要になります。
ドメインモデルをシングルトンにする¶
一番最初に思いつく方法が、ドメインモデル(またはドメインモデル管理者)をシングルトンとして設計する方法です。
public class FooModel {
private static FooModel INSTANCE = new FooModel();
private FooModel() {
}
public FooModel getInstance() {
return INSTANCE;
}
:
}
--------
public class FooController implements Initializable {
@FXML
private TextField fooField;
@FXML
private void handleFooAction(ActionEvent event) {
fooField.setText(FooModel.getInstance().getFooName();
}
:
}
FXMLに対応するコントローラを取得してドメインモデルを設定¶
Applicationクラスがドメインモデル・インスタンスを生成してから、FXMLに対応するControllerにそのドメインモデル・インスタンスを設定します。
クラス図にすると次のようになります。
Application(Main)クラスで、FXMLをロードしたあとにFXMLに対応するControllerクラスを取得します。
public class FooApp extends Application {
@Override
public void start(Stage stage) throws Exception {
FXMLLoader loader = new FXMLLoader(getClass().getResource("Foo.xml"));
Parent root = loader.load();
FooModel model = new FooModel();
FooController controller = (FooController) loader.getController();
controller.setModel(model);
:
}
:
}
---------------
public class FooController implements Initializable {
private FooModel fooModel;
:
public void setModel(FooModel model) {
fooModel = model;
}
:
}
実行時にドメインモデルを検索する仕組み¶
ネーミングサービス的な機能(同一プロセス内でも)を設けて、そこからドメインモデルのインスタンスを取得します。
といっても標準的なネーミングサービス的な機能はないので、自前で作ることになるかと思います。
具体例は略
DI(Dependency Injection)を使う¶
おそらく、SpringフレームワークとJavaFXを併用するような形態と思います。
DIコンテナ(フレームワーク)の採用は、アプリケーション全体にインパクトを与えます。
具体例は略