假设我有一个swing GUI,它有文本字段和按钮。当我单击按钮时,我想将该值保存在数据库中的文本中,并返回选项窗格"success"消息。
我过去的做法是
模型:JDBC类
视图:GUI:在该按钮的"执行的操作"操作中,我使用参数调用save方法
Controller con = new Controller();
con.save(text1.getText());
控制器:编写一个保存方法
JDBC db = new
public void save(jTextfeild text){
text= text1.getText();
boolean b= db.putData("insert into .. values(text)");
if(b){
JOptionPane("Success");
}
}
我就是这样开始的。但后来我明白了这不是应该的,这是完全不安全和愚蠢的。
我真的很想学习如何在MVC中正确地做到这一点。请用一个小例子向我解释一下。谢谢你抽出时间。
在Swing这样的东西中,这是一个很难理解的主题,Swing已经使用了一种MVC形式,尽管更像VC-M,其中模型与视图和控制器分离,但视图和控制器是组合的。
想想JButton
,当用户按下某个键或用鼠标点击它时,你没有提供控制器来管理它是如何被触发的,这是在内部完成的,当发生时,你会收到有关操作的通知。
考虑到这一点,您需要允许视图进行半自我管理。例如,根据您的需求,视图将有一个按钮和文本字段。
视图本身将管理用户和按钮本身之间的交互(例如,维护内部ActionListener
),但随后将向控制器提供控制器可能感兴趣的任何状态更改的通知
在更纯粹的MVC意义上,视图和模型不会相互了解,而控制器会管理它们。这与Swing的工作方式有点矛盾,因为Swing允许您将模型直接传递到视图,请参阅任何Swing组件。
这并不意味着你不能让事情发挥作用,但你需要知道这个概念在哪里会动摇,或者需要"按摩"才能更好地发挥作用。
通常,当我处理这些类型的事情时,我会后退一步,看看更广阔的前景。
- 您有一个可以接受文本并生成文本或对其进行更改的视图
- 您有一个模型,它可以加载和修改文本,但几乎不提供其他事件
- 您有一个控制器,它希望从模型中获取文本并将其提供给视图,并监视视图对文本的更改并在模型中更新这些更改
现在,MVC与"代码到接口(而不是实现)"的概念配合得非常好,在这种程度上,我倾向于从契约开始。。。
查看合同
public interface TextView {
public void setText(String text);
public String getText();
public void addTextViewObserver(TextViewObserver observer);
public void removeTextViewObserver(TextViewObserver observer);
}
public interface TextViewObserver {
public void textWasChanged(TextView view);
}
现在,视图的要求之一是在文本以某种有意义的方式发生变化时生成事件,为此,我使用了一个简单的观察者模式来实现。现在你可以争辩说控制器是观察者,但在我看来,控制器可能有我不想暴露在视图中的功能(比如模型)
合同范本
接下来是模型。。。
public interface TextModel {
public String getText();
public void setText(String text);
}
真的很简单。现在,您可能会考虑在这些方法中添加某种Exception
,以允许模型因某种原因失败,但Exception
应该尽可能通用(甚至是自定义Exception
),这样,如果需要,您就可以替换实现
控制器合同
最后,控制器。。。
public interface TextViewController {
public TextView getTextView();
public TextModel getTextModel();
}
再说一遍,非常简单。您可能对控制器有更复杂的要求,但对于本例,这几乎是我们真正需要的。
实施
查看
public class TextViewPane extends JPanel implements TextView {
private JTextField textField;
private JButton updateButton;
private List<TextViewObserver> observers;
public TextViewPane() {
observers = new ArrayList<>(25);
textField = new JTextField(25);
updateButton = new JButton("Update");
updateButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
fireTextWasChanged();
}
});
setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridwidth = GridBagConstraints.REMAINDER;
add(textField, gbc);
add(updateButton, gbc);
}
@Override
public void setText(String text) {
textField.setText(text);
}
@Override
public String getText() {
return textField.getText();
}
@Override
public void addTextViewObserver(TextViewObserver observer) {
observers.add(observer);
}
@Override
public void removeTextViewObserver(TextViewObserver observer) {
observers.remove(observer);
}
protected void fireTextWasChanged() {
for (TextViewObserver observer : observers) {
observer.textWasChanged(this);
}
}
}
模型
public class SimpleTextModel implements TextModel {
private String text = "This is some text";
@Override
public String getText() {
return text;
}
@Override
public void setText(String text) {
this.text = text;
}
}
控制器
public class SimpleTextController implements TextViewController, TextViewObserver {
private TextView view;
private TextModel model;
public SimpleTextController(TextView view, TextModel model) {
this.view = Objects.requireNonNull(view, "TextView can not null");
this.model = Objects.requireNonNull(model, "TextModel can not be null");
view.addTextViewObserver(this);
}
@Override
public TextView getTextView() {
return view;
}
@Override
public TextModel getTextModel() {
return model;
}
@Override
public void textWasChanged(TextView view) {
getTextModel().setText(view.getText());
}
}
把它放在一起
TextViewPane view = new TextViewPane();
TextModel model = new SimpleTextModel();
TextViewController controller = new SimpleTextController(view, model);
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(view);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
现在,所有这些只是一个可能的解决方案的例子。例如,您可以有一个控制器实现,它具有模型或视图的特定实现,或者两者都有。
关键是,你不应该在意。控制器不关心视图是如何实现的,只关心它将生成textWasChanged
事件。模型根本不关心视图(反之亦然),控制器也不关心模型,只关心它会获取和设置一些文本。
对于一个更复杂的例子,您可以看看Java和GUI——根据MVC模式,ActionListener属于哪里?
经过思考
- 这只是解决这个问题的一种可能方法。例如,您可以将视图限制为一个观察者
- 你应该一直在想"我能改变MVC的任何一个部分吗?它还能工作吗?"这让你思考改变实现的任何一部分可能会对周围的合同产生的问题。你应该明白,如何实现每一层并不重要
- 视图可以充当另一个子视图的控制器(或者充当子视图的另一控制器的容器)。这有时会吓到人们,但视图可以充当一个或多个子控制器/视图的父容器,这允许您开发复杂的UI
- 不要在合同中公开实现细节,例如,模型不应该抛出
SQLException
,因为另一个实现可能不基于基于SQL的解决方案。不要公开UI元素,这意味着所有实现都需要实现这些元素。如果我想要一个向用户显示JComboBox
而不是JTextField
的视图实现,会发生什么?这也是我不在视图约定中使用ActionListener
的原因,因为我不知道视图的实现实际上是如何生成textWasChanged
事件的