プロジェクト

全般

プロフィール

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 |
          |(Main)クラス|
          +------------+
              | |
     +-------+ +-----+
     ↓          ↓
+----------+      +--------------+
|FXMLで定義|----->|FXMLに対応する|
|したView  |<-----|Controller    |
+----------+      +--------------+

この図でViewとControllerを双方向結合としているのは、FXMLでControllerのメソッドをアクションとして指定しているなどの依存があるためです。
JavaFXでは、このようにVとCとが密に双方向結合しているので厳密にはMVC構造にはなりません。そのためか、MVPとして説明されることが多いようです。

このJavaFXアプリケーションの基本構造にはアプリケーションのデータ(ドメインモデルとかビジネスロジックとかいわれる)を置く場所が明確に定められていません。
通常、先に述べた「モデルとビューの分離」原則にもとづき、ドメインデータはビュー(GUI)に依存しないように設けます。
そこで、クラス間の関連が片方向となる次の構造を取ることが多いです。

          +------------+
          |Application |
          |(Main)クラス|
          +------------+
              | |
     +-------+ +-----+
     ↓          ↓
+----------+      +--------------+      +---------+
|FXMLで定義|----->|FXMLに対応する|----->|ドメイン |
|したView  |<-----|Controller    |      |モデル   |
+----------+      +--------------+      +---------+

このドメインモデルの入れ方が、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に対応する|----->|ドメイン |
|したView  |<-----|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コンテナ(フレームワーク)の採用は、アプリケーション全体にインパクトを与えます。

具体例は略

クリップボードから画像を追加 (サイズの上限: 1 GB)