ActionListener在Java GUI App的控制器中是个好主意吗?

我不想尝试遵循MVC模式。 在互联网上,我看到最着名的例子是计算器,例如这里 。 我开始使用这种MVC模式的实现。 但是现在我对控制器中的动作侦听器有些疑虑,因为它们倾向于移动查看。

有很多变化与视图相关的主要原因 – 字体,颜色,边框等。此外还有仅仅修改视图的actionlisteners! 因此,在控制器中实现这样的actionlistener要困难得多(与视图中的简单内部匿名类相比)。 此外,它需要从控制器访问许多视图元素。

我有一个想法是在控制器和一些视图中保留一些actionlisteners,但它可能导致将来混乱。 所以我想听听其他人的想法。

PS这个问题与许多ActionListener的MVC模式不重复

MVC不是一种“严格”的模式。 对原始模式有不同的解释,并且经常使用不同的衍生物,如MVP或MVVM (即使人们说他们使用的是MVC)。

最重要的方面是将模型和视图分开。 但有关它们如何连接的详细信息可能会有所不同,具体取决于应用案例。

MVC模式最常出现的问题是: “什么是控制器?”

答案:

“获得晋升的会计师”

根据我的个人经验,很少有理由有一个明确的 “控制器”类。 强制在一个“控制器”类中累积和汇总监听器有几个严重的缺点。 为了在GUI组件和Model之间建立连接,您有两个选择:一个选项是允许访问视图组件以附加侦听器:

 gui.getSomeButton().addActionListener(myActionListener); 

我认为这是不可取的,因为它暴露了实现细节并阻碍了修改。 另一种选择更好 – 即提供允许附加侦听器的方法:

 gui.addActionListenerToSomeButton(myActionListener); 

但我认为这是值得怀疑的,因为它仍然暴露了一个按钮的事实。 例如,当您有一个JTextField输入一个数字,然后将其更改为JSlider时,问题可能会变得更加明显:它将更改所需的Listener类型,尽管它应该只是视图的问题。

在Swing应用程序中,我认为Listener可以被视为“小控制器”。 我认为拥有直接调用模型方法的匿名侦听器是完全可行的(除非有这些调用的附加逻辑)。

话虽如此:我不会考虑您作为MVC的“好”示例链接的示例。 首先,因为所选示例没有显示MVC的关键点:模型不包含状态 ,并且MVC中的模型通常是必须被观察的事实(因此,作为Listeners附加)不清楚。 其次,由于上面提到的要点,因此建立GUI和模型之间的连接的方式是有问题的。

我喜欢http://csis.pace.edu/~bergin/mvc/mvcgui.html上的例子。 它的一部分也可能受到质疑(例如,使用通用的Observer / Observable类),但我认为它以令人信服的方式很好地展示了MVC的基本概念。


编辑 :没有以ZIP的forms下载此示例。 但您可以将TemperatureModelTemperatureGUIFarenheitGUIMVCTempConvert复制并粘贴到IDE中。 (它假定存在CelsiusGUI 。此CelsiusGUI ,在网站上省略,但在结构上等于Farenheit GUI。对于第一个测试,实例化它的行可能只是被注释掉)。

添加侦听器的选项在此示例中由抽象的TemperatureGUI类提供。 实际的侦听器由具体的 FarenheitGUI类创建和附加。 但这或多或少是一个实现细节。 这里的关键点(也是针对原始问题)是监听器是由View 创建的,以内部类甚至匿名类的forms。 这些侦听器直接调用模型的方法。 即,将温度设置为Farenheit(对于Farenheit GUI),或设置以摄氏度为单位的温度(对于Celsius GUI)。

仍有一定程度的自由。 它不是一个“完美”或“通用”的MVC示例。 但是,到目前为止,我发现的恕我直言比其他大多数MVC更好,因为它很好地展示了重要的方面:

  1. 该模型是Observable
  2. 视图是一个Observer
  3. “控制器”(即本例中的监听器)是由视图维护的匿名/内部类,以及模型的调用方法

在更复杂的常规设置中,不会使用Observable / Observer类。 相反,人们可以为模型创建专用的侦听器和可能的事件。 在这种情况下,这可能类似于TemperatureChangedListenerTemperatureChangedEvent 。 为简洁起见,此处使用了Observable / Observer类,因为它们已经是标准API的一部分。

Agin,请注意,可能存在更复杂的应用案例,其中在这个小例子中概述的MVC的想法必须稍微扩展。 特别是,当要执行的任务不仅仅是调用模型的方法时。 例如,当View包含多个输入字段时,在将数据传递给模型之前,必须对此数据进行预处理或以其他方式validation。 这样的任务应该由匿名听众完成。 相反,这些任务可以概括在一个类中, 然后可以称为“控制器”。 但是, 实际侦听器附加到GUI组件仍然只能由View完成。 作为一个过于暗示的例子:这可能会发生

 // In the view: someButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { String s = someTextField.getText(); Date d = someFormattedTextField.getDate(); int i = someSlider.getValue(); // The controller validates the given input, and // eventually calls some methods on the Model, // possibly using the given input values controller.process(s, i, d); } }); 

我通常遵循以下设计模式,它适用于我,因为它非常有效地分离模型视图和控制器

视图 :您的视图应包含所有jcomponents及其getter或setter,组件放置相关代码,布局相关代码。 我从不向视图类中的任何组件添加任何侦听器,它只处理布局

模型 :模型应该有占位符来保存视图的数据,例如:如果你有一个JTable,模型可能包含一个arraylist,它将保存你的JTable的数据

Controller = Model + view(这里是你将模型与视图绑定的地方)在这里添加所有的监听器绑定你的文本字段,combobox等添加你的客户端业务逻辑为你的按钮添加你的动作监听器这是你应该访问你的视图的地方和模型。

希望这可以帮助。

如果您遵循Controller GRASP模式 (假设您将接口和域层分开actionPerformed() ,则actionPerformed()代码将保留在接口层中。 书中解释了这个概念 ,但为了清楚起见,我在本书的教师资源中加入了一个图:

在此处输入图像描述

编辑:正如我在上面的评论中所说,如果您实施不同的视图(例如,在移动应用上,或添加语音识别),您希望保持发送到您的域层的相同系统操作。 对我而言,我喜欢视图层“识别转换为控制器命令的用户手势”的概念。

所有逻辑都应放在控制器中。 但是没有严格的行,一些非常简单的逻辑可以在视图中很好。 将actionListeners放在控制器中,然后在GUI中放置按钮。 这就是我通常做的事情。 有时我很懒,让我的按钮实现actionListeners让他们自己听,在这种情况下我把按钮放在控制器包里面。