这是一个难以掌握的主题,例如 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 and GUI - Where do ActionListeners belong according to MVC pattern?
经过思考
- 这只是解决问题的一种可能方法。例如,您可以将视图限制为单个观察者。
- 您应该一直在想“我可以更改 MVC 的任何部分并且它仍然可以工作吗?”这使您考虑更改实施的任何一部分可能对周围合同产生的可能问题。您应该明白,每一层的实现方式并不重要
- 视图可以充当另一个子视图的控制器(或充当子视图的另一个控制器的容器)。这有时会吓到人们,但视图可以充当一个或多个子控制器/视图的父容器,这允许您开发复杂的 UI
- 不要在合约中公开实现细节,例如,模型不应该抛出
SQLException,因为另一个实现可能不是基于基于 SQL 的解决方案。不要暴露 UI 元素,这意味着所有实现都需要实现这些元素。如果我想要一个向用户呈现JComboBox 而不是JTextField 的视图实现会发生什么?这也是我不在视图契约中使用ActionListener 的原因,因为我不知道视图的实现如何实际生成textWasChanged 事件