web-dev-qa-db-ja.com

Java Swingでモデルをビュー/コントローラから完全に分離する方法

Java SwingアプリでModelクラスをView/Controllerクラスから分離するための一般的に合意された設計ガイドラインのコレクションはありますか?View/Controllerが知っていることはそれほど心配していません他の方法としてのモデルについては何もありません:私はモデルをjavax.swingの何も知らないように設計したいと思います。理想的には、CLIのようなプリミティブによって駆動できるようにする単純なAPIが必要です。大まかに言えば「エンジン」である必要があります。

モデルへのGUIイベントの伝達はそれほど難しくありません。アクション実行者はモデルのAPIを呼び出すことができます。しかし、モデルがGUIに反映する必要がある独自の状態変更を行う場合はどうでしょうか?それが「聞く」ことの目的ですが、「聞く」ことさえ完全に受動的ではありません。モデルがリスナーの追加について知っている必要があります。

考えさせられた特定の問題は、ファイルのキューに関係しています。 GUI側では、DefaultListModelの後ろにJListがあり、ファイルシステムからファイルを選択してJListに追加するためのGUIがいくつかあります。モデル側では、この「キュー」の下部からファイルをプルし(JListからファイルが消えるようにします)、何らかの方法でファイルを処理します。実際、Modelコードはすでに作成されています。現在、_ArrayList<File>_を維持し、パブリックadd(File)メソッドを公開しています。しかし、モデルにSwing固有の大きな変更を加えずに、ビュー/コントローラーでモデルを機能させる方法について、私は途方に暮れています。

私はJavaとGUIプログラミングの両方に非常に慣れており、これまで「バッチ」および「バックエンド」プログラミングを常に行ってきました。したがって、モデルとUI、それが可能で、教えることができる場合。

10
Chap

MVCには一般に合意された(つまりdefacto)設計ガイドラインはありません。自分で行うのはそれほど難しいことではありませんが、クラスの計画と多くの時間と忍耐が必要です。

明確な解決策がない理由は、MVCを実行する方法が複数あり、すべて長所と短所があるためです。だから、それについて賢くなり、あなたに最も適したことをしてください。

あなたの質問に答えるために、実際にはコントローラーもビューから切り離したいと考えています(そのため、Swingアプリとコンソールアプリの両方に同じビジネスルールロジックを使用できます)。 Swingの例では、コントローラーをJWindowおよびSwingのウィジェットから切り離す必要があります。 (実際のフレームワークを使用する前に)使用した方法は、コントローラーが使用するビューのインターフェースを作成することです。

public interface PersonView {
    void setPersons(Collection<Person> persons);
}

public class PersonController {

    private PersonView view;
    private PersonModel model;

    public PersonController(PersonView view, PersonModel model) {
        this.view = view;
        this.model = model;
    }
    // ... methods to affect the model etc. 
    // such as refreshing and sort:

    public void refresh() {
        this.view.setPersons(model.getAsList());
    }

    public void sortByName(boolean descending) {
       // do your sorting through the model.
       this.view.setPersons(model.getSortedByName());
    }

}

起動時のこのソリューションでは、コントローラーをビューに登録する必要があります。

public class PersonWindow extends JWindow implements PersonView {

    PersonController controller;
    Model model;

    // ... Constructor etc.

    public void initialize() {
        this.controller = new PersonController(this, this.model);

        // do all the other swing stuff

        this.controller.refresh();
    }

    public void setPersons(Collection<Person> persons) {
        // TODO: set the JList (in case that's you are using) 
        // to use the given parameter
    }

}

代わりにすべての設定を行うIoCコンテナーを作成することをお勧めします。

とにかく、この方法では、同じコントローラーを使用して、コンソールのみのビューを実装できます。

public class PersonConsole implements PersonView {

    PersonController controller;
    Model model;

    public static void main(String[] args) {
        new PersonConsole().run();
    }

    public void run() {
        this.model = createModel();
        this.controller = new PersonController(this, this.model);

        this.controller.refresh();
    }

    public void setPersons(Collection<Person> persons) {
        // just output the collection to the console

        StringBuffer output = new StringBuffer();
        for(Person p : persons) {
            output.append(String.format("%s%n", p.getName()));
        }

        System.out.println(output);
    }

    public void createModel() {
        // TODO: create this.model
    }

    // this could be expanded with simple console menu with keyboard
    // input and other console specific stuff

}    

楽しい部分は、イベント処理を行う方法です。これを実装するには、インターフェイスを使用してビュー自体をコントローラーに登録します。これはObserverパターンを使用して行われます(.NETを使用している場合は、代わりにイベントハンドラーを使用します)。これは、ドキュメントが保存またはロードされたときに信号を送る簡単な「ドキュメントオブザーバー」の例です。

public interface DocumentObserver {
    void onDocumentSave(DocModel saved);
    void onDocumentLoad(DocModel loaded);
}

// in your controller you implement register/unregister methods
private List<DocumentObserver> observers;

// register observer in to the controller
public void addObserver(DocumentObserver o) {
    this.observers.add(o);
}

// unregisters observer from the controller
public void removeObserver(DocumentObserver o) {
    this.observers.remove(o);
}

public saveDoc() {
    DocModel model = model.save();
    for (DocumentObserver o : observers) {
        o.onDocumentSave(model);
    }
}

public loadDoc(String path) {
    DocModel model = model.load(path);
    for (DocumentObserver o : observers) {
        o.onDocumentLoad(model);
    }        
}

このようにして、ビューはドキュメントの更新をサブスクライブしているため、ビュー自体を適切に更新できます。 DocumentObserverインターフェースを実装するだけです。

public class DocumentWindow extends JWindow 
        implements DocView, DocumentObserver {

    //... all swing stuff

    public void onDocumentSave(DocModel saved) {
        // No-op
    }

    public void onDocumentLoad(DocModel loaded) {
        // do what you need with the loaded model to the
        // swing components, or let the controller do it on
        // the view interface
    }

    // ...

}

これらのやる気を起こさせる例が、自分でそれを行う方法についてのいくつかのアイデアを提供してくれることを願っています。 ただし、Javaでフレームワークを使用することを検討することを強くお勧めします。そうしないと、多くの定型文が作成されます。書くのに長い時間がかかるコードです。アプリケーションなど、必要になる可能性が最も高い基本機能のいくつかを実装するために使用できるリッチクライアントプラットフォーム(RCP)がいくつかあります。幅広いドキュメント処理と多くの基本的なイベント処理。

私の頭から考えることができるいくつかがあります: Eclipse および Netbeans RCPです。

自分でコントローラーとモデルを開発する必要がありますが、それがORMを使用する理由です。例は Hibernate です。

IoCコンテナーはすべてがクールですが、これのためのフレームワークもあります。 Spring など(特にデータ処理も行います)。

10
Spoike

これについての私の見解は時には妥協が必要です

あなたが言うように、観測されたオブジェクトがこれのための明示的なインフラストラクチャを持たずに、変更通知を暗黙的に伝達できたら素晴らしいでしょう。 Java、C#、C++などの一般的な命令型言語の場合、ランタイムアーキテクチャは現在のところとにかく軽量すぎます。つまり、現時点では言語仕様の一部ではありません。

あなたの特定のケースでは、INotifyPropertyChanged(c#のように)などの一般的なインターフェイスを定義/使用することは、決して悪いことではないと思います。 変更した場合はお知らせします.

繰り返しになりますが、変更通知を自分で定義する必要がない場合はすばらしいと思いますが、すべてのクラスで暗黙的な場合はオーバーヘッドが発生する可能性があります。

0
Max