一个MVC例子
MVC模式将代码分为三个部分:模型、视图、控制器。其中,模型定义数据以及对数据的操作接口。视图用于展示模型中的数据给用户,同时相应用户的操作,将用户的操作传递给控制器,控制器根据用户的操作执行相应的业务逻辑,访问或修改模型数据,同时控制器可以根据操作更新视图。另外,数据模型的更新可以通过观察者模式通知视图或者控制器。典型的MVC模式如下图,它们之间的交互通过方法调用或者事件模型来完成:
在Apple公司的Cocoa框架中,模型不直接与视图进行交互,它们之间的交互都通过控制器来传递,因而实现模型和控制器的解耦。此时,控制器同时监听数据模型和视图界面,根据它们的变化实现相应的逻辑控制。即:控制器可以访问和操作数据模型,模型的数据更新会通知控制器。视图的用户交互也会通知控制器,控制器可以更新视图或者响应用户操作。
下面是一个简单计算的例子,用户界面如下图:
这个简单界面实现连续乘法,在Input输入一个数,第一次输入时,点击Multiply按钮将这个数与1相乘,结果显示在Total中,第二次以后的输入,将Input中的数和Total中的数相乘。Clear按钮可以将Total恢复到初始值1.如果输入的数不是整数,弹出对话框提示用户。
在这个例子中,我们用到了上面描述的MVC之间的各种交互,先给出代码,首先是模型类:
/**
* @author Brandon B. Lin
*
*/
public class CalcModel extends Observable {
private static final String INITIAL_VALUE = "1";
private BigInteger total;
CalcModel() {
reset();
}
public void reset() {
total = new BigInteger(INITIAL_VALUE);
setChanged();
notifyObservers();
}
public void multiplyBy(String operand) {
total = total.multiply(new BigInteger(operand));
setChanged();
notifyObservers();
}
public void setTotal(String newValue) {
total = new BigInteger(newValue);
setChanged();
notifyObservers();
}
public String getTotal() {
return total.toString();
}
这个模型继承了抽象类Observable,如果Total的值发生变化,会通知实现注册的观察者,即下面的视图类:
/**
* @author Brandon B. Lin
*
*/
class CalcView extends JFrame implements Observer {
private static final long serialVersionUID = 5000467452832579495L;
private static final String INITIAL_VALUE = "1";
private JTextField userInputTextField = new JTextField(5);
private JTextField totalTextField = new JTextField(20);
private JButton multiplyButton = new JButton("Multiply");
private JButton clearButton = new JButton("Clear");
private CalcModel model;
CalcView(CalcModel model) {
this.model = model;
this.model.addObserver(this);
this.model.setTotal(INITIAL_VALUE);
totalTextField.setText(model.getTotal());
totalTextField.setEditable(false);
init();
}
private void init() {
JPanel content = new JPanel();
content.setLayout(new FlowLayout());
content.add(new JLabel("Input"));
content.add(userInputTextField);
content.add(multiplyButton);
content.add(new JLabel("Total"));
content.add(totalTextField);
content.add(clearButton);
this.setContentPane(content);
this.pack();
this.setTitle("Simple Calc - MVC");
// The window closing event should probably be passed to the
// Controller in a real program, but this is a short example.
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
void reset() {
totalTextField.setText(INITIAL_VALUE);
}
String getUserInput() {
return userInputTextField.getText();
}
void setTotal(String newTotal) {
totalTextField.setText(newTotal);
}
void showError(String errMessage) {
JOptionPane.showMessageDialog(this, errMessage);
}
void addMultiplyListener(ActionListener mal) {
multiplyButton.addActionListener(mal);
}
void addClearListener(ActionListener cal) {
clearButton.addActionListener(cal);
}
@Override
public void update(Observable arg0, Object arg1) {
setTotal(model.getTotal());
}
视图类实现观察者接口Observer,初始化图像界面,响应用户操作,同时提供了注册监听器的两个接口。视图持有模型的引用,在模型变化的时候使用模型的数据来更新界面。当用户与视图交互时,作为响应,视图通知实现注册的监听器,用户操作与监听器之间的逻辑关系在控制器类中绑定。
/**
* @author Brandon B. Lin
*
*/
public class CalcController {
private CalcModel model;
private CalcView view;
CalcController(CalcModel model, CalcView view) {
this.model = model;
this.view = view;
view.addMultiplyListener(new MultiplyListener());
view.addClearListener(new ClearListener());
}
class MultiplyListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
String userInput = "";
try {
userInput = view.getUserInput();
model.multiplyBy(userInput);
} catch (NumberFormatException nfex) {
view.showError("Bad input: '" + userInput + "'");
}
}
}
class ClearListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
model.reset();
}
}
}
控制器持有视图和模型的引用,在这个类中,定义了用户操作与监听器之间的逻辑关系,也就是说,对于用户的某个操作,程序该如何响应(调用哪个监听器中的方法)在这个类中定义。另外,控制器可以访问模型,更新视图或者响应用户,例如上面的方法调用:view.showError()。
下面是一个测试类,即实例化各个对象:
public class CalcMVC {
public static void main(String[] args) {
CalcModel model = new CalcModel();
CalcView view = new CalcView(model);
new CalcController(model, view);
view.setVisible(true);
}
}
在上面的实现中,MVC三者之间的关系较为复杂,它们之间的耦合也比较紧密。可以采用事件模型来解耦模型和视图,它们之间只通过中介控制器来通信。