すぐに、少し長い投稿をお詫びする必要がありますが、これはかなり長い間私を悩ませてきました。私は最近、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に配置する」ことは解決策ではないため、これについてあなたの助けとコメントを得ようとしています. あなたのアイデアを前もって感謝します。