Java MVC - 我在这里遗漏了什么吗?

时间:2012-12-05 15:09:42

标签: java swing model-view-controller mvvm

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

我在C#/ .Net 4.0中完成了所有的GUI编程,虽然不是很广泛但是足够广泛,可以很好地理解MVVM - 这是MVC的新版本。这是一个非常简单的概念:您使用XAML(类似于comnponents的描述的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
    }
}

很明显,这不是一段简单的代码,尽管很短。现在,如果我们要使用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: View不应该知道有关Controller的任何信息,而应该响应模型中的更新。

为了克服上述情况,我们可以作为模型。模型只有两个List,每个组合框一个。所以我们有一个模型(完全uselsess),一个视图和控制器...

问题2 我们应该如何连线?至少有两种不同的技术:直接与观察者模式

问题3 直接接线 - 是不是只是将起始设置中的所有内容重写为三个单独的类?在这种方法中,View注册一个模型,Controller同时具有视图和模型。它看起来像......:

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;
    }
}

这比它需要的更复杂,恕我直言,使模型和控制器始终相互调用:view - &gt; (做某事)控制器 - &gt; (更新自己)视图

问题4 观察者模式 - 在我看来这更糟糕,尽管它允许我们解耦视图和模型。视图将在模型上注册为侦听器,这将通知视图有关任何更改。现在,我们需要一个方法:

public void addListener(ViewListener listener);

我们需要一个ViewListener。现在,我们可能有一个带有一些事件参数的方法,但我们不能用一种方法来满足所有场景。例如,View如何知道我们只是更新第二个组合框并且没有重置所有值,或者没有禁用某些东西,或者没有从表中删除项目???因此,我们需要为每次更新提供一个单独的方法(几乎将我们在gui上的方法复制并粘贴到监听器中)使监听器变得庞大。

主要问题

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

主要问题1 将loginc拆分为多个对象:如果您想象有多个面板,并且有许多控件,那么您将拥有所有这些面板的视图,模型和视图,从而导致三次通常允许在UI类上完成工作,因为你可以拥有许多类。

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

由于“将所有内容放入用户界面”是的解决方案,我正在努力寻求您的帮助和意见。非常感谢你的想法。

1 个答案:

答案 0 :(得分:6)

我亲自带着观察者模式走了。我认为你夸大了方法的复杂性。

你的模型应该是“无用的”,因为它只包含数据并向感兴趣的听众发送事件。这是整个优势。您可以将任何业务逻辑和需求封装在一个类中,并将其完全独立于任何特定视图进行测试。根据您希望如何显示数据,您甚至可以重复使用具有不同视图的相同模型。

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

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

至于你的反对意见 - 课程数量是红鲱鱼。这是一个比解耦低得多的优先级指标。如果您真的想要优化类的数量,请将所有逻辑放在名为Main的类中。添加沟通方法 - 再次,你是解耦的东西。这是OOP的优势之一。