【发布时间】:2015-12-04 13:26:09
【问题描述】:
鉴于多个问题excellent_informative_for_me - trashgod 的回答、that 和 that 以及其他几个没有回答我的问题的问题,
一个关于 ActionListeners 的类应该如何设计位置
(以及整体 MVC 分离 - 下文将详细解释)。
目录
- 问题解释
- 我的示例的文件结构树 (4)
- 编译/清理源命令 (4)
- 来源
1。问题解释
我读过关于 MVC 的内容,并且我认为我了解其中的大部分内容,为了这个问题,让我们假设这是真的。不再赘述:
- 视图是根据控制器请求从模型生成的。
在大多数实现中,View 可以访问 Model 实例。 - 控制器与用户交互,将更改传播到模型和视图。
- 在极端简化的情况下,模型是数据的容器。
可以通过 View 观察到。
现在,我的困惑在于 ActionListeners - 应该注册哪个类 - 并且反过来还包含 - 按钮代码,或者实际上是大多数 View 元素的代码,它们实际上不仅仅是指示器,而是模型操纵器?
假设我们在视图中有两个项目 - 用于更改模型数据的按钮和一些仅用于更改视图外观的可视项目。让代码负责更改 View 类中的 View 外观似乎是合理的。我的问题与第一种情况有关。我有几个想法:
- View 创建按钮,因此在 View 中创建 ActionListener 并同时注册回调是很自然的。 但这要求 View 有与模型相关的代码,打破了封装。 View 应该对底层的 Controller 或 Model 知之甚少,只能通过 Observer 与之交谈。
- 我可以公开按钮等视图项,并从 Controller 将 ActionListener 附加到它们,但这又会破坏封装。
- 我可以为每个按钮实现一些回调 - View 会询问控制器是否有任何代码应该注册为给定按钮名称的 ActionListener,但这似乎过于复杂,并且需要同步控制器和视图之间的名称。
- 我可以假设,理智的 ;),TableFactory 中的按钮可能是公开的,允许将 ActionListener 注入任何代码。
- 控制器可以替换整个视图项目(创建按钮并替换现有的)但这看起来很疯狂,因为它不是它的角色
2。我的例子的文件结构树(4)
.
└── test
├── controllers
│ └── Controller.java
├── models
│ └── Model.java
├── resources
│ └── a.properties
├── Something.java
└── views
├── TableFactory.java
└── View.java
3。源代码的编译/清理命令 (4)
编译:
- javac test/Something.java test/models/*.java test/controllers/*.java test/views/*.java
运行:
- java test.Something
清理:
- 找到 . -iname "*.class" -exec rm {} \;
4。来源
此代码还包含国际化存根,为此我提出了单独的问题,这些行已明确标记,不应对答案产生任何影响。
控制器 => Controller.javapackage test.controllers;
import test.models.Model;
import test.views.View;
public class Controller {
// Stub - doing nothing for now.
}
模型 => 模型.java
package test.models;
import java.util.Observable;
public class Model extends Observable {
}
东西.java
package test;
import test.views.View;
import test.models.Model;
import test.controllers.Controller;
public class Something {
Model m;
View v;
Controller c;
Something() {
initModel();
initView();
initController();
}
private void initModel() {
m = new Model();
}
private void initView() {
v = new View(m);
}
private void initController() {
c = new Controller(m, v);
}
public static void main(String[] args) {
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
Something it = new Something();
}
});
}
}
查看 => 查看.java
package test.views;
import java.awt.*; // layouts
import javax.swing.*; // JPanel
import java.util.Observer; // MVC => model
import java.util.Observable; // MVC => model
import test.models.Model; // MVC => model
import test.views.TableFactory;
public class View {
private JFrame root;
private Model model;
public JPanel root_panel;
public View(Model model){
root = new JFrame("some tests");
root.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
root_panel = new JPanel();
root_panel.add(new TableFactory(new String[]{"a", "b", "c"}));
this.model = model;
this.model.addObserver(new ModelObserver());
root.add(root_panel);
root.pack();
root.setLocationRelativeTo(null);
root.setVisible(true);
}
}
class ModelObserver implements Observer {
@Override
public void update(Observable o, Object arg) {
System.out.print(arg.toString());
System.out.print(o.toString());
}
}
查看 => TableFactory.java
package test.views;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import javax.swing.*;
import javax.swing.table.DefaultTableModel;
public class TableFactory extends JPanel {
private String[] cols;
private String[] buttonNames;
private Map<String, JButton> buttons;
private JTable table;
TableFactory(String[] cols){
this.cols = cols;
buttonNames = new String[]{"THIS", "ARE", "BUTTONS"};
commonInit();
}
TableFactory(String[] cols, String[] buttons){
this.cols = cols;
this.buttonNames = buttons;
commonInit();
}
private void commonInit(){
this.buttons = makeButtonMap(buttonNames);
DefaultTableModel model = new DefaultTableModel();
this.table = new JTable(model);
for (String col: this.cols)
model.addColumn(col);
setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS));
JPanel buttons_container = new JPanel(new GridLayout(1, 0));
for (String name : buttonNames){
buttons_container.add(buttons.get(name));
}
JScrollPane table_container = new JScrollPane(table);
this.removeAll();
this.add(buttons_container);
this.add(table_container);
this.repaint();
}
private Map<String, JButton> makeButtonMap(String[] cols){
Map<String, JButton> buttons = new HashMap<String, JButton>(cols.length);
for (String name : cols){
buttons.put(name, new JButton(name));
}
return buttons;
}
}
编辑(回应下面的 cmets)
下一个信息来源here
经过深思熟虑后,我理解了 Olivier 的评论,后来气垫船充满了鳗鱼的详细信息……javax.swing.Action => setAction 是我要走的路。 Controller 访问 View 的 JPanel,获取对包含按钮或任何 JComponent 的映射的引用,并向其添加操作。 View 不知道控制器代码中的内容。当我使它正常工作时,我会更新这个答案,所以在这里绊倒的任何人都可能拥有它。
只有两件事让我担心(都非常罕见,但仍然如此):
- 由于我公开了 View 添加操作的方法,实际上我相信任何人都只能从控制器或视图添加它。但是如果模型可以访问视图 - 它也可以覆盖操作。
- 压倒一切。如果我设置了某个对象的动作,然后忘记并从不同的地方设置另一个动作,它就消失了,这可能会使调试变得困难。
【问题讨论】:
-
如果你想完全解耦 Swing 作为视图,你应该在你的视图中创建你的
ActionListeners。此外,您应该考虑在可能的情况下使用javax.swing.Action而不是ActionListener。 -
视图可以将 ActionListeners 添加到 JButtons,但您可以让侦听器简单地调用 Control 方法,或更改视图“有界”属性的状态,该属性在更改时通知侦听器到视图,通过调用视图的属性更改支持的通知方法之一。或者去@OlivierGrégoire 的路线并使用AbstractActions,如果需要,这些可以通过控件“注入”到视图中。
-
Olivier - 我现在不太确定我是否想将它们解耦。也许从长远来看,努力不值得? @HovercraftFullOfEels - 我知道你提到了我的第一个子弹和第三个子弹?我不确定我是否遵循从“更改视图有界属性的状态”开始的确切句子含义 - 请您重新措辞,也许详细说明或只使用更简单的词?
-
注意,你不会“暴露”按钮等等。您只公开允许添加侦听器或操作的公共方法。关于“绑定”属性,请查看JavaBean Properties Tutorial。请注意,Swing GUI 天生就支持这些,因为每个 Swing 组件都有自己的 SwingPropertyChangeSupport 字段。
-
@JustMe:你只听你想响应的属性。 For example.
标签: java swing model-view-controller