4

すぐに、少し長い投稿をお詫びする必要がありますが、これはかなり長い間私を悩ませてきました。私は最近、MVC について多くのことを読み、Java の Swing の世界で MVC がどのように位置付けられているかを読みましたが、チュートリアルが提供する単純なおもちゃの例よりも少し複雑なアプリケーションで、MVC がリモートで役立つ理由をまだ理解できません。しかし、最初から始めましょう...

私はすべての GUI プログラミングを C#/.Net 4.0 で行いました。これは広範囲ではありませんでしたが、MVVMを十分に理解するには十分でした。これは MVC の新しいバージョンです。これは非常に単純な概念です。たとえば、テーブルとそのモデルの間のバインディング、テキスト フィールドの文字列値を指定して、XAML (XML のようなコンポーネントの記述) を使用して GUI を定義します。これらのバインディングは、完全に個別に定義するオブジェクト プロパティに対応します。そうすれば、ビューと他の世界との間の完全な切り離しができます。さらに、モデル内のすべての変更は「ほぼ」自動的に対応するコントロールに反映され、イベント駆動型の設計はより中心的なものになります。

さて、Java に戻ると、古い学校の MVC を使用する必要があります。非常に簡単な例から始めましょう。2 つのコンボ ボックスと 1 つのボタンを備えたパネルを作成しようとしています。最初のコンボ ボックスで値を選択すると、2 番目のコンボ ボックスの値が駆動され、2 番目のコンボ ボックスで値を選択すると、両方のコンボ ボックスの値に基づいて外部サービスが呼び出され、ボタンは外部サービスを使用して最初のコンボ ボックスの値をリセットします。同じように。「私の」アプローチを使用してそれを行う場合、次のように進めます。

public class TestGUI {
    private JComboBox<String> firstCombo;
    private JComboBox<String> secondCombo;
    private JButton button;

    private ExternalReloadService reloadService;
    private ExternalProcessingService processingService;

    public TestGUI(ExternalReloadService reloadService, ExternalProcessingService processingService) {
        initialise();
        this.reloadService = reloadService;
        this.processingService = processingService;
    }

    private void initialise() {
        firstCombo = new JComboBox<>();
        secondCombo = new JComboBox<>();
        button = new JButton("Refresh");

        firstCombo.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                String value = (String) ((JComboBox) e.getSource()).getSelectedItem();
                reloadSecondCombo(value);
            }
        });

        secondCombo.addPropertyChangeListener(new PropertyChangeListener() {
            @Override
            public void propertyChange(PropertyChangeEvent evt) {
                if (evt.getPropertyName().equals("model")) {
                    ComboBoxModel model = (ComboBoxModel) evt.getNewValue();
                    if (model.getSize() == 0) {
                        String value = (String) model.getSelectedItem();
                        processValues((String) firstCombo.getSelectedItem(), value);
                    }
                }
            }
        });

        secondCombo.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                processValues((String) firstCombo.getSelectedItem(), (String) secondCombo.getSelectedItem());
            }
        });

        button.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                resetValues()
            }


        });
    }

    private void processValues(String selectedItem, String value) {
        processingService.process(selectedItem, value);
        //possibly do sth with result and update ui
    }

    private void reloadSecondCombo(String value) {
        secondCombo.setModel(new CustomModel(reloadService.reload(value)));
    }

    private void resetValues() {
        //Call other external service to pull default data, possibly from DB
    }
}

短いとはいえ、これが単純なコードではないことは明らかです。さて、MVC を使用してそれを行う場合、私の最初のステップは、ある種のコントローラーを使用することです。

public class TestGUI {
    private JComboBox<String> firstCombo;
    private JComboBox<String> secondCombo;
    private JButton button;

    private Constroller controller;

    public TestGUI(Controller controller) {
        this.controller = controller;
        initialise();
    }

    private void initialise() {
        firstCombo = new JComboBox<>();
        secondCombo = new JComboBox<>();
        button = new JButton("Refresh");

        firstCombo.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                String value = (String) ((JComboBox) e.getSource()).getSelectedItem();
               Data d = controller.getReloadedData(value);
               //assiign to combobox
            }
        });

問題 1:ビューはコントローラーについて何も認識してはならず、むしろモデルからの更新に応答する必要があります。

上記を克服するために、私たちはモデルとしてできました。モデルには、コンボボックスごとに 1 つずつ、合計 2 つの List があります。したがって、モデル(完全に役に立たない)、ビュー、およびコントローラーがあります...

問題 2これをどのように配線するか? 少なくとも 2 つの個別の手法があります: 直接 vs オブザーバー パターン

問題 3直接配線 - それは、最初のセットアップのすべてを 3 つの別々のクラスに書き直すだけではありませんか? このアプローチでは、ビューはモデルを登録し、コントローラーはビューとモデルの両方を持っています。次のようになります。

public class TestGUI {
    private JComboBox<String> firstCombo;
    private JComboBox<String> secondCombo;
    private JButton button;

    private Model model;

    public TestGUI(Model m) {
        model = m;
    }

   public void updateSecondValues(){
       model.getSecondValues();
       //do sth
   }
}

public class Controller {

    private TestGUI view;
    private Model model;

    public reloadSecondValues(){
        firstValues = ...//reload using external service
        model.setSecondValues(firstValues);
        view.updateSecondValues();
    }

}

public class Model {

    private Set<String> firstValues;
    private Set<String> secondValues;

    public Set<String> getFirstValues() {
        return firstValues;
    }

    public void setFirstValues(Set<String> firstValues) {
        this.firstValues = firstValues;
    }

    public Set<String> getSecondValues() {
        return secondValues;
    }

    public void setSecondValues(Set<String> secondValues) {
        this.secondValues = secondValues;
    }
}

これは必要以上に複雑です。IMHO、モデルとコントローラーを常に互いに呼び出します: ビュー -> (do sth) コントローラー -> (自分で更新) ビュー

問題 4オブザーバー パターン - ビューとモデルを切り離すことはできますが、これは私の意見ではさらに悪いことです。ビューはモデルのリスナーとして登録され、変更についてビューに通知されます。SO 今、次のようなメソッドが必要です。

public void addListener(ViewListener listener);

ViewListener が必要です。現在、いくつかのイベント パラメータを持つ 1 つのメソッドを潜在的に持つことができますが、1 つのメソッドですべてのシナリオに対応することはできません。たとえば、ビューは、2 番目のコンボボックスを更新しているだけで、すべての値をリセットしていないこと、何かを無効にしていないこと、またはテーブルから項目を削除していないことをどのように認識しますか? したがって、更新ごとに個別のメソッドが必要になり (ほとんどの場合、GUI にあるメソッドをリスナーにコピー アンド ペーストします)、リスナーが巨大になります。

主な問題

ここでいくつかの問題を提起したので、それを少しまとめたいと思います。

主な問題 1 loginc を複数のオブジェクトに分割: 多くのコントロールを備えた複数のパネルがあると想像すると、それらすべてのビュー、モデル、およびビューがあり、通常の 3 倍のクラスが存在することになります。 UIクラスで作業を行います。

主な問題 2どのような配線手法を使用しても、すべてのオブジェクトにメソッドを追加して通信を許可することになります。これは、UI にすべてを配置するだけでは冗長になります。

「すべてをUIに配置する」ことは解決策ではないため、これについてあなたの助けとコメントを得ようとしています. あなたのアイデアを前もって感謝します。

4

1 に答える 1

6

私は個人的にオブザーバーパターンを使用しました。アプローチの複雑さを誇張していると思います。

モデルは単にデータを含み、関心のあるリスナーにイベントを発生させるという点で「役に立たない」べきです。それが全体の利点です。ビジネス ロジックと要件を 1 つのクラスにカプセル化し、特定のビューとは完全に個別に単体テストを行うことができます。データの表示方法によっては、同じモデルを異なるビューで再利用できる場合もあります。

コントローラーは、モデルの変更を担当します。ビューはモデルからイベントを受け取りますが、ユーザー入力に基づいて変更を加えるために、コントローラーを経由します。ここでも利点は、デカップリングとテスト容易性です。コントローラは、GUI コンポーネントから完全に分離されています。特定のビューに関する知識はありません。

ビューは、データへの特定のインターフェイスを表し、特定の操作を提供します。ビューの構築にモデルとコントローラーが必要であることはまったく適切です。ビューはそのリスナーをモデルに登録します。それらのリスナー内では、独自の表現が更新されます。適切な UI テスト フレームワークがある場合は、これらのイベントをモックし、実際のモデルを使用せずにビューが正常に更新されたことをアサートできます。これには、データベースや Web サービスなどの外部サービスが必要になる場合があります。ビュー内の UI コンポーネントが独自のイベントを受け取ると、コントローラーを呼び出すことができます。ここでも、適切なテスト フレームワークを使用すると、ネットワーク呼び出しなどの実際の操作を実際に呼び出すことなく、モックされたコントローラーがこれらのイベントを受け取ることをアサートできます。

あなたの異議については、クラスの数は赤いニシンです。これは、デカップリングよりも優先度の低い指標です。本当にクラス数を最適化したい場合は、すべてのロジックを という名前のクラスに入れますMain。通信手段を追加する -- 繰り返しますが、物事を分離しています。これは、OOP の利点の 1 つです。

于 2012-12-05T15:24:12.407 に答える