Java MVC - 我在这里遗漏了什么吗? [英] Java MVC - Am I missing something here?

查看:53
本文介绍了Java MVC - 我在这里遗漏了什么吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

我在 C#/.Net 4.0 中完成了我所有的 GUI 编程,这并不广泛,但足够深入了解 MVVM - 这是 MVC 的新版本.这是一个非常简单的概念:您使用 XAML(类似 XML 的组件描述)定义 GUI,指定例如之间的绑定.表及其模型,文本字段的字符串值.这些绑定对应于完全单独定义的对象属性.这样,您就可以在视图和世界其他地方之间完全脱钩.最重要的是,模型中的所有更改几乎"都会自动回火到相应的控件,事件驱动的设计更加中心化等等.

现在,回到 Java,我们需要使用老式的 MVC.让我从一个非常简单的例子开始:我试图有一个带有两个组合框和一个按钮的面板.在第一个组合框中选择值将驱动第二个组合框的值,在第二个组合框中选择一个值将根据两个组合框中的值调用外部服务,按钮将使用外部服务重置第一个组合中的值以及.如果我使用我的"方法来做,我会按如下方式进行:

公共类TestGUI {私有 JComboBox第一个组合;私有 JComboBox第二个组合;私人 JButton 按钮;私有 ExternalReloadService reloadService;私有外部处理服务处理服务;public TestGUI(ExternalReloadService reloadService, ExternalProcessingService processingService) {初始化();this.reloadService = reloadService;this.processingService = processingService;}私有无效初始化(){firstCombo = new JComboBox<>();secondCombo = new JComboBox<>();button = new JButton("刷新");firstCombo.addActionListener(new ActionListener() {@覆盖public void actionPerformed(ActionEvent e) {字符串值 = (String) ((JComboBox) e.getSource()).getSelectedItem();reloadSecondCombo(value);}});secondCombo.addPropertyChangeListener(new PropertyChangeListener() {@覆盖公共无效propertyChange(PropertyChangeEvent evt){if (evt.getPropertyName().equals("model")) {ComboBoxModel 模型 = (ComboBoxModel) evt.getNewValue();如果(模型.getSize()== 0){String value = (String) model.getSelectedItem();processValues((String) firstCombo.getSelectedItem(), value);}}}});secondCombo.addActionListener(new ActionListener() {@覆盖public void actionPerformed(ActionEvent e) {processValues((String) firstCombo.getSelectedItem(), (String) secondCombo.getSelectedItem());}});button.addActionListener(new ActionListener() {@覆盖public void actionPerformed(ActionEvent e) {重置值()}});}private void processValues(String selectedItem, String value) {processingService.process(selectedItem, value);//可能对结果做某事并更新ui}私有无效 reloadSecondCombo(字符串值){secondCombo.setModel(new CustomModel(reloadService.reload(value)));}私有无效重置值(){//调用其他外部服务拉取默认数据,可能是从DB}}

很明显,这不是一段简单的代码,虽然很短.现在,如果我们要使用 MVC 来做到这一点,我的第一步将是使用某种控制器,这将完成所有工作,例如

公共类TestGUI {私有 JComboBox第一个组合;私有 JComboBox第二个组合;私人 JButton 按钮;私有控制器控制器;公共 TestGUI(控制器控制器){this.controller = 控制器;初始化();}私有无效初始化(){firstCombo = new JComboBox<>();secondCombo = new JComboBox<>();button = new JButton("刷新");firstCombo.addActionListener(new ActionListener() {@覆盖public void actionPerformed(ActionEvent e) {字符串值 = (String) ((JComboBox) e.getSource()).getSelectedItem();数据 d = controller.getReloadedData(value);//分配给组合框}});

问题 1: 视图不应了解控制器的任何信息,而应响应模型的更新.

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

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

问题 3 直接连线 - 这不是将初始设置中的所有内容都重写为三个独立的类吗?这种方式中,View注册了一个模型,Controller同时拥有了View和Model.它看起来像……

公共类TestGUI {私有 JComboBox第一个组合;私有 JComboBox第二个组合;私人 JButton 按钮;私有模型模型;公共 TestGUI(Model m) {模型 = 米;}public void updateSecondValues(){模型.getSecondValues();//做某事}}公共类控制器{私有 TestGUI 视图;私有模型模型;公共 reloadSecondValues(){firstValues = ...//使用外部服务重新加载模型.setSecondValues(firstValues);view.updateSecondValues();}}公共类模型{私有集第一个值;私有集第二个值;公共集getFirstValues() {返回第一个值;}public void setFirstValues(Set firstValues) {this.firstValues = firstValues;}公共集getSecondValues() {返回第二个值;}public void setSecondValues(Set secondValues) {this.secondValues = secondValues;}}

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

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

public void addListener(ViewListener listener);

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

主要问题

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

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

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

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

解决方案

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

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

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

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

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

Straight away I need to apologise for a bit lengthy post, but this has been bugging me for quite some time now. I have read a lot about MVC recently, and how it has its place within the Swing world of Java, and I still cannot understand why this is even remotely useful in any application being slightly more complex than the simple toy examples the tutorials provide. But let me start from the beginning...

I did all my GUI programming in C#/.Net 4.0, which was not extensive but extensive enough to get a good understanding of MVVM - this is a new version of MVC. It is quite a simple concept: You define you GUI using XAML (XML like description of comnponents), specifying bindings between e.g. table and its model, string values of text fields. These bindings correspond to object properties, which you define completely separately. That way, you have a complete decoupling between view and the rest of the world. On top, all changes within the model are "almost" automatically fired back to corresponding controls, the event driven design is much more central etc. etc.

Now, coming back to Java, we need to use an old school MVC. Let me begin with a very simple example: I am trying to have a panel with two combo boxes and one button. Selecting value in the first combo would drive the values of the second combo box, selecting a value in the second would call an external service, based on values in both combo boxes, and the button would reset values in the first combo, using external service as well. If I were to do it using "my" approach, I would proceed as follows:

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

It's obvious that this is not a simple piece of code, although short. Now, if we were to do it using MVC my first step would be to use some sort of controller, that would do all the work, e.g.

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

Problem 1: View should not know anything about the Controller, but should rather respond to the updates from the model.

To overcome the above, we could as the model. Model would simply have two List, one for each combobox. So we have a model (completely uselsess), a view, and controller...

Problem 2 How should we wire this? There are at least 2 separate techniques: direct vs Observer pattern

Problem 3 Direct wiring - isn't that just rewriting everything in the starting setup into three separate classes? In this approach, View registers a model, and Controller has both view and Model. It would look as sth like:

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

This is way more complicated than it needs to, IMHO, making model and controller call each other all the time: view -> (do sth) controller -> (update yourself) view

Problem 4 Observer pattern - this is even worse in my opinion, although it allows us to decouple view and model. View would be registered as a listener on the model, which would inform view about any changes. SO now, we need a method like:

public void addListener(ViewListener listener);

and we need a ViewListener. Now, we could potentially have one method with some event params, but we cannot cater for ALL scenarios with one method. For instance, how would View know that we are just updating the second combobox and not resetting all values, or not disabling something, or not removing an item froma table??? We would therefore, need a separate method for each update, (pretty much copy and paste the methods we would have on the gui into the listener) making a listener huge.

Main Problems

As I have raised a few issues here, I wanted to summarise that a bit.

Main Probelm 1 Splitting loginc into several objects: If you imagine you have multiple panels, with many controls, you would have a view, model and views for all of them, resulting in three times as many classes as you would have normally, by allowing to do the work on the UI class.

Main problem 2 No matter what wiring technique you use, you end up adding methods on all objects to allow the communication, which would be redundant, had you simply placed everything in the UI.

As "placing everything in the UI" is NOT a solution, I am trying to get your help and comments on this. Many thanks in advance for your ideas.

解决方案

I've personally gone with the observer pattern. I think you're overstating the complexity of an approach.

Your model should be "useless" in that it simply contains data and fires events to interested listeners. That's the whole advantage. You can encapsulate any business logic and requirements in one class and unit test it entirely separately of any particular view. You may even be able to reuse the same model with different views depending on how you want to display the data.

The controller is responsible for mutating the model. The view receives events from the model, but to make changes based on user input, it goes through the controller. The advantage here again is decoupling and testability. The controller is entirely separate from any GUI components; it has no knowledge of a particular view.

Your view represents a particular interface into data and provides certain operations on it. It's perfectly appropriate that constructing a View requires a Model and Controller. The View will register its listeners on the Model. Inside those listeners it will update its own representation. If you have a decent UI testing framework you can mock these events and assert that the view has updated successfully without using the real model, which may require some external service like a database or web service. When the UI components in the View receive their own events, they can call the Controller -- again, with a good testing framework you can assert that a mocked Controller receives these events without actually invoking any real operations, such as network calls.

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.

这篇关于Java MVC - 我在这里遗漏了什么吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆