【问题标题】:Java MVC - Am I missing something here? [closed]Java MVC - 我在这里遗漏了什么吗? [关闭]
【发布时间】:2012-11-23 10:56:00
【问题描述】:

我需要马上为这篇冗长的帖子道歉,但这已经困扰了我很长一段时间了。我最近阅读了很多关于 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 将在模型上注册为 listener,它将通知 view 任何更改。所以现在,我们需要一个类似的方法:

public void addListener(ViewListener listener);

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

主要问题

因为我在这里提出了一些问题,所以我想总结一下。

主要问题 1 将 loginc 拆分为多个对象:如果您想象您有多个面板,带有许多控件,那么您将拥有一个视图、模型和所有视图,结果是 3 倍通过允许在 UI 类上完成工作,可以像往常一样拥有许多类。

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

由于“将所有内容放在 UI 中”不是解决方案,因此我正在努力寻求您的帮助和解决方案。非常感谢您的想法。

【问题讨论】:

  • 不确定这是否会有所帮助,但我在尝试学习来自 MVVM 背景的 MVC 时也遇到了问题,并且发现 this answer 在理解这两种模式之间的差异方面帮助了我很多。
  • 我不同意那个帖子——控制器不应该生成模型(视图模型)——它应该是视图模型
  • 我建议你检查 Swing 应用程序的 MVP (Model-View_presenter) 模式。 12
  • 好评——虽然其中一些东西基于 C# 而不是 Java
  • 第二个链接提供了两个 Java 示例。 JGoodies 也有关于这种模式的演示。

标签: java swing model-view-controller mvvm


【解决方案1】:

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

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

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

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

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

【讨论】:

  • 好的,就我同意解耦而言,您将如何处理可能有 10 个不同面板的事实,每个面板都有自己的模型和控制器?每个视图都有一个侦听器接口吗?以及在每个模型上注册这种类型的侦听器的模型?
  • 是的。 Java 没有 C# 和 XAML 提供的强大数据绑定。我想你可以尝试连接到 PropertyChangeListener 或类似的。我可能会做的是将侦听器接口定义为模型的内部类,而 View 内部的实现将是匿名内部类。 8 中的 Lambda 支持将使事情变得更容易。
  • 你的意思是 Model 会暴露一个公共的内部类,而 View 会像 V​​iew 实现 Model.Listener 一样实现它?
  • View 不一定要实现它。你以前用过匿名内部类吗?您可以在 View 的构造函数中执行以下操作: model.registerFooListener(new FooListener() { public void foo(Event e) { //在此处对视图执行操作,例如更新 ComboBox } });
  • 是的,但是观察者模式的重点是永远不要将模型传递给视图。它应该是在 model.register(view) 中进行注册的控制器
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-02-01
  • 1970-01-01
  • 1970-01-01
  • 2021-10-24
  • 1970-01-01
  • 1970-01-01
  • 2021-01-18
相关资源
最近更新 更多