4

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

我在 C#/.Net 4.0 中完成了我所有的 GUI 编程,这并不广泛,但足够广泛,可以很好地理解MVVM——这是 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
    }
}

很明显,这不是一段简单的代码,虽然很短。现在,如果我们要使用 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:视图不应该知道任何关于控制器的信息,而是应该响应模型的更新。

为了克服上述问题,我们可以作为模型。模型只会有两个列表,每个组合框一个。所以我们有一个模型(完全无用)、一个视图和控制器......

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

这比它需要的要复杂得多,恕我直言,让模型和控制器一直相互调用:视图->(做某事)控制器->(更新自己)视图

问题 4观察者模式——在我看来这甚至更糟,尽管它允许我们解耦视图和模型。View 将被注册为模型上的监听器,它将通知视图任何更改。所以现在,我们需要一个类似的方法:

public void addListener(ViewListener listener);

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

主要问题

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

主要问题 1 将 loginc拆分为多个对象:如果您想象您有多个面板,带有许多控件,那么您将拥有一个视图、模型和所有视图,通过允许做 UI 类的工作。

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

由于“将所有内容都放在 UI 中”不是解决方案,因此我正试图获得您的帮助和对此的评论。非常感谢您的想法。

4

1 回答 1

6

我个人使用了观察者模式。我认为您夸大了方法的复杂性。

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

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

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

As for your objections -- number of classes is a red herring. That's a much lower priority metric than decoupling. If you really wanted to optomize number of classes, put all your logic in a class named Main. Adding methods for communication -- again, you're decoupling things. That's one of the advantages to OOP.

于 2012-12-05T15:24:12.407 回答