JavaMVC-我是否遗漏了什么?

JavaMVC-我是否遗漏了什么?,java,swing,model-view-controller,mvvm,Java,Swing,Model View Controller,Mvvm,我需要马上为这篇有点长的帖子道歉,但这已经困扰了我很长一段时间了。我最近读了很多关于MVC的书,以及它在Java的Swing世界中是如何占有一席之地的,但我仍然无法理解为什么它在任何比教程中提供的简单玩具示例稍微复杂的应用程序中都有一点用处。但让我从头开始 我所有的GUI编程都是在C#/.NET4.0中完成的,虽然不够广泛,但足以让我很好地理解——这是MVC的一个新版本。这是一个非常简单的概念:您使用XAML(类似XML的组件描述)定义GUI,指定表与其模型、文本字段的字符串值等之间的绑定。这些

我需要马上为这篇有点长的帖子道歉,但这已经困扰了我很长一段时间了。我最近读了很多关于MVC的书,以及它在Java的Swing世界中是如何占有一席之地的,但我仍然无法理解为什么它在任何比教程中提供的简单玩具示例稍微复杂的应用程序中都有一点用处。但让我从头开始

我所有的GUI编程都是在C#/.NET4.0中完成的,虽然不够广泛,但足以让我很好地理解——这是MVC的一个新版本。这是一个非常简单的概念:您使用XAML(类似XML的组件描述)定义GUI,指定表与其模型、文本字段的字符串值等之间的绑定。这些绑定对应于对象属性,您可以完全单独定义它们。这样,视图和世界其他部分之间就完全脱钩了。最重要的是,模型中的所有更改都“几乎”自动回传给相应的控件,事件驱动的设计更为核心等等

现在,回到Java,我们需要使用老式的MVC。让我从一个非常简单的例子开始:我试图拥有一个包含两个组合框和一个按钮的面板。在第一个组合框中选择值将驱动第二个组合框的值,在第二个组合框中选择值将基于两个组合框中的值调用外部服务,并且按钮将使用外部服务重置第一个组合框中的值。如果我使用“我的”方法,我将按照以下步骤进行:

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
    }
}
我们需要一个viewstener。现在,我们可能有一个带有一些事件参数的方法,但我们不能用一个方法满足所有场景。例如,View如何知道我们只是在更新第二个组合框而没有重置所有值,或者没有禁用某些内容,或者没有从表中删除某个项???因此,我们需要为每个更新使用一个单独的方法(相当于将gui上的方法复制并粘贴到侦听器中),从而使侦听器变得巨大

主要问题

由于我在这里提出了一些问题,我想总结一下

主要问题1将loginc拆分为多个对象:如果您设想有多个面板和多个控件,则所有面板、模型和视图都有一个视图、模型和视图,通过允许对UI类进行操作,生成的类是正常情况下的三倍

主要问题2无论您使用何种布线技术,最终都会在所有对象上添加方法以允许通信,如果您只是将所有内容都放在UI中,这将是多余的


由于“将所有内容都放在UI中”不是一个解决方案,我正试图得到您的帮助和意见。非常感谢您的建议。

我个人支持观察者模式。我认为你夸大了一种方法的复杂性

您的模型应该是“无用的”,因为它只包含数据并向感兴趣的侦听器触发事件。这就是全部优势。您可以将任何业务逻辑和需求封装在一个类中,并将其与任何特定视图完全分开进行单元测试。您甚至可以使用不同的视图重用同一模型,具体取决于您希望如何显示数据

控制器负责改变模型。视图从模型接收事件,但要根据用户输入进行更改,它需要通过控制器。这里的优势是解耦和可测试性。控制器完全独立于任何GUI组件;它不知道某个特定的视图

您的视图将一个特定的接口表示为数据,并对其提供某些操作。构建视图需要模型和控制器是非常合适的。视图将在模型上注册其侦听器。在这些侦听器中,它将更新自己的表示。如果您有一个像样的UI测试框架,那么您可以模拟这些事件,并断言视图已成功更新,而无需使用实际模型,这可能需要一些外部服务,如数据库或web服务。当视图中的UI组件接收到它们自己的事件时,它们可以调用控制器——同样,通过良好的测试框架,您可以断言模拟控制器接收到这些事件,而不需要实际调用任何实际操作,例如网络调用


至于你的反对意见——课程的数量是个骗局。这是一个比解耦低得多的优先级指标。如果您确实想优化类的数量,请将所有逻辑放在名为
Main
的类中。添加通信方法——同样,您正在解耦事情。这是OOP的优势之一。

不确定这是否会有帮助,但我在尝试从MVVM背景学习MVC时也遇到了问题,发现这对我理解这两种模式之间的差异有很大帮助。我不同意post-Controller不应生成模型(ViewModels)-它应该是ViewModel,我建议您检查swing应用程序的MVP(Model-View_presenter)模式。好的评论——虽然有些内容是基于C语言而不是Java语言的,但第二个链接提供了两个Java示例。JGoodies也有关于此模式的演示。好吧,我同意解耦,您如何处理可能有10个不同面板的事实,每个面板都有自己的模型和控制器?每个视图是否有一个侦听器接口?还有一个模型在每个模型上注册这种类型的侦听器?是的。Java没有C#和XAML提供的强大数据绑定。我想,你可以尝试加入PropertyChangeListener或类似的网站。我可能会将侦听器接口定义为模型的一个内部类,而im
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
            }
        });
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;
    }
}
public void addListener(ViewListener listener);