抱歉,这篇文章可能有点冗长,但我已经被困扰了一段时间。最近我阅读了很多关于MVC的内容,以及它在Java Swing领域中的应用,但我仍然无法理解为什么它对于任何比简单玩具示例更复杂的应用程序都有用。但让我们从头开始......
我所有的GUI编程都是在C#/.Net 4.0上完成的,虽然不是很广泛,但足够深入了解MVVM——这是MVC的一个新版本。它是一个非常简单的概念:您使用XAML(组件的XML描述)定义GUI,指定例如表格及其模型、文本字段的字符串值之间的绑定。这些绑定对应于对象属性,您需要完全分开定义。这样,您就可以完全解耦视图和其他部分。此外,所有模型内的更改“几乎”自动地反馈到相应的控件中,事件驱动设计也更加核心等等。
现在回到Java,我们需要使用传统的MVC。让我从一个非常简单的例子开始:我想要一个带有两个组合框和一个按钮的面板。在第一个组合框中选择值会驱动第二个组合框中的值,在第二个组合框中选择值会调用外部服务,基于两个组合框中的值,而按钮将使用外部服务重置第一个组合框中的值。如果我要使用“我的”方法来完成这个任务,我将按照以下步骤进行:
很明显,尽管代码很短,但这不是一个简单的代码片段。如果我们要使用MVC来完成它,我的第一步将是使用某种控制器来完成所有工作,例如:
为了克服上述问题,我们可以向模型询问。模型将简单地拥有两个列表,每个下拉框一个。因此,我们有一个(完全无用的)模型、一个视图和一个控制器... 问题2: 如何连接它们?至少有两种不同的技术:直接连接与观察者模式。 问题3: 直接连接 - 那不仅是将初始设置中的所有内容重写为三个独立的类吗?在这种方法中,视图注册一个模型,而控制器同时具有视图和模型。它看起来像这样:
这比必要的复杂得多,我认为,模型和控制器一直在相互调用:视图 ->(执行某些操作)控制器 ->(更新自己)视图。
问题4 观察者模式 - 在我看来,这甚至更糟糕,尽管它允许我们解耦视图和模型。视图将作为模型的监听器进行注册,模型将通知视图有关任何更改的信息。因此,现在我们需要一个像这样的方法:
我们需要一个ViewListener。现在,我们可能有一个带有一些事件参数的方法,但我们不能用一个方法来处理所有情况。例如,视图如何知道我们只是更新第二个组合框而不是重置所有值、禁用某些内容或从表格中删除项?因此,我们需要为每个更新创建一个单独的方法(基本上将我们在GUI上拥有的方法复制并粘贴到监听器中),使监听器变得庞大。 主要问题 由于我在这里提出了一些问题,我想简要总结一下。 主要问题1 将逻辑拆分为几个对象:如果您想象您有多个面板,每个面板都有许多控件,您将需要为其创建视图、模型和视图,这会导致您正常情况下需要三倍数量的类,因为允许在UI类上进行工作。 主要问题2 无论您使用什么连线技术,最终都会在所有对象上添加方法以允许通信,这将是多余的,如果您简单地将所有内容放在UI中。
作为“将所有内容放在UI中”不是解决方案,我正在尝试获得您的帮助和意见。非常感谢您提前的想法。
我所有的GUI编程都是在C#/.Net 4.0上完成的,虽然不是很广泛,但足够深入了解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: 直接连接 - 那不仅是将初始设置中的所有内容重写为三个独立的类吗?在这种方法中,视图注册一个模型,而控制器同时具有视图和模型。它看起来像这样:
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 观察者模式 - 在我看来,这甚至更糟糕,尽管它允许我们解耦视图和模型。视图将作为模型的监听器进行注册,模型将通知视图有关任何更改的信息。因此,现在我们需要一个像这样的方法:
public void addListener(ViewListener listener);
我们需要一个ViewListener。现在,我们可能有一个带有一些事件参数的方法,但我们不能用一个方法来处理所有情况。例如,视图如何知道我们只是更新第二个组合框而不是重置所有值、禁用某些内容或从表格中删除项?因此,我们需要为每个更新创建一个单独的方法(基本上将我们在GUI上拥有的方法复制并粘贴到监听器中),使监听器变得庞大。 主要问题 由于我在这里提出了一些问题,我想简要总结一下。 主要问题1 将逻辑拆分为几个对象:如果您想象您有多个面板,每个面板都有许多控件,您将需要为其创建视图、模型和视图,这会导致您正常情况下需要三倍数量的类,因为允许在UI类上进行工作。 主要问题2 无论您使用什么连线技术,最终都会在所有对象上添加方法以允许通信,这将是多余的,如果您简单地将所有内容放在UI中。
作为“将所有内容放在UI中”不是解决方案,我正在尝试获得您的帮助和意见。非常感谢您提前的想法。