•
Model:
模型代表核心业务数据,和核心的业务功能,因此,Model应该被设计成独立于特定的输入行为和输出表示的程序。
•
View:
视图模块将模型的中的数据显示给用户。而因为相同的数据可以有不同形式的显示方法,所以一个模型可以有很多个视图view。
•
Controller:
每个view可以拥有与之相关联的controller组件。Controllers负责处理用户输入,通常是从鼠标运动,按钮,和键盘事件引起的输入。然后负责调用模型的相应功能。
数据更新:改变-传播机制(change-propagation)
- 如果用户通过一个view的controller改变了model,所有的其它的view必须反映出该改变。
- 当数据发生变化的时候,model通知所有的view,告诉他们数据已经改变了;
- Views可以遍历model中的数据,以便发现到底是什么改变了。然后更新显示数据。
- 改变-传播机制(change-propagation)机制保证了用户界面和模型的一致性。这个改变-传播机制可以由观察着模式的Publisher-Subscriber方式实现。
MVC的优点
1) 容易增加或者改变视图
- 事务逻辑被封装在Model中,所以在新增加一个视图的时候,不必要改动模型,而是因为事务逻辑都是一样的,所以只需要新增加一个视图类即可。
2)容易独立地更新每个独立的软件模块
- 由于一个应用被分离为三个软件模块,因此,我们可以独立地改变其中的一个模块,而不影响其它两个模块。例如,一个应用的业务流程或者业务规则的改变只需改动MVC的模型层。
3)有利于软件的工程化管理
- 由于不同的层各司其职,每一层不同的应用具有某些相同的特征,有利于通过工程化、工具化产生管理程序代码。例如,使用工具生成图形界面(View)
Design Examples Using the MVC Design Pattern
设计方案3工作原理
- 用户输入. 用户通过使用 CarAuctionGUI 中的Cars列表选择待卖的车,点击Search,以便获取车的图片;然后输入拍价,点击Bit,给出价格。
- 捕捉用户输入. 每次点击按钮产生的事件,都被Controller对象捕捉,然后调用 actionPerformed() 方法。
- 更新模型数据. 在actionPerformed()方法中,将根据事件的类型,分别使用setSelectedCar 或者setBitPrice 方法更新CarModel的数据
- Model通知View. 由于CarGUIView、CarBitView与CarModel之间存在观察者/被观察者的关系,所以当CarModel的状态改变了的时候,CarGUIView和CarBitView的update()方法将被自动调用。
- 更新View. 在update()方法中,调用CarModel的getSelectedCar 或者getBitPrice 方法得到最新的数据,然后根据数据,更新视图。
设计要点
- CarModel实现了interface Observable,实现了方法notifyObservers()和register(Observerobs)。
- CarGUIInfoView和CarBitInfoView实现了interfaceObserver, 实现了方法update()。
- 每个CarGUIInfoView和CarBitInfoView都将自己注册为CarModel的观察者。每当CarModel的状态改变的时候,其方法notifyObservers将改变告诉给它所有的观察者,观察者的update()可以自动查询CarModel,了解到底是什么状态改变了,从而作出响应的反应。
- 事实上,notifyObservers()的代码中,直接调用了观察者中的update()方法。以下是代码。
public class CarAuctionGUI extends JFrame {
static public void main(String argv[]) {
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
cm = new CarModel();
cgiv = new CarGUIInfoView(cm);
cbiv = new CarBitInfoView(cm);
cm.register(cgiv); // 将视图对象注册到模型
cm.register(cbiv); // 将视图对象注册到模型
CarAuctionGUI frame = new CarAuctionGUI();
}
});
}
}
public class Controller implements ActionListener {
private CarAuctionGUI objCarGUI;
private CarModel cm;
private String carPrice;
public void actionPerformed(ActionEvent e) {
String searchResult = null;
if (e.getActionCommand().equals(SEARCH)) {
String selectedCar = objCarGUI.getSelectedCar();
cm.setSelectedCar(selectedCar); // 更新模型数据
cm.setCarFileUrl();
cm.setupImageIcon();
cm.setSearchBtnClickInfo(true);
cm.notifyObservers(); // 通知观察者
cm.setSearchBtnClickInfo(false);
}
if (e.getActionCommand().equals(CarAuctionGUI.BIT)) {
carPrice = objCarGUI.getBitPrice();
cm.setBitPrice(carPrice); // 更新模型数据
cm.setBitBtnClickInfo(true);
cm.notifyObservers(); // 通知观察者
cm.setBitBtnClickInfo(false);
}
}
}
public class CarModel implements Observable {
private ArrayList<Observer> observersList;
private ImageIcon imgIcon;
private URL url;
private String[] carNameList;
private String carSelected;
private String bitPrice;
private boolean isBitBtnClicked = false;
private boolean isSearchBtnClicked = false;
static final String CARFILES = "CarFiles/";
static final String CARIMAGES = "CarImages/";
public CarModel() {
observersList = new ArrayList<Observer>();
carNameList = new String[200];
}
public void setCarList(String[] cars) {
carNameList = cars;
}
public String[] getCarList() {
return carNameList;
}
public void setSelectedCar(String sCar) {
carSelected = sCar;
}
public String getSelectedCar() {
return carSelected;
}
public void setBitPrice(String bPrice) {
bitPrice = "";
bitPrice = bitPrice + bPrice;
}
public String getBitPrice() {
return bitPrice;
}
public void setCarFileUrl() {
String fileURLStr = CARFILES + carSelected + ".html";
File file = new File(fileURLStr);
URI uri = file.toURI();
url = uri.toURL();
}
public URL getCarFileURL() {
return url;
}
public void setupImageIcon() {
String iconStr = CARIMAGES + carSelected + ".jpg";
imgIcon = createImageIcon(iconStr);
}
public ImageIcon getImageIcon() {
return imgIcon;
}
public void setBitBtnClickInfo(boolean opt) {
isBitBtnClicked = opt;
}
public boolean isBitBtnClicked() {
return isBitBtnClicked;
}
public void setSearchBtnClickInfo(boolean opt) {
isSearchBtnClicked = opt;
}
public boolean isSearchBtnClicked() {
return isSearchBtnClicked;
}
public void register(Observer obs) {
observersList.add(obs);
}
public void notifyObservers() {
for (int i = 0; i < observersList.size(); i++) {
Observer observer = (Observer) observersList.get(i);
observer.update(this);
}
}
protected ImageIcon createImageIcon(String path) {
URL imgURL = getClass().getResource(path);
if (imgURL != null) {
return new ImageIcon(imgURL);
} else {
return null;
}
}
} // End of class
class CarBitInfoView extends JFrame implements Observer {
private JLabel bitLabel;
private JTextArea bitText;
private CarModel model;
private JScrollPane textPane;
public CarBitInfoView(CarModel cmodel) {
model = cmodel;
bitLabel = new JLabel("Bits offered:");
bitText = new JTextArea(4, 20);
bitText.setFont(new Font("Serif", Font.PLAIN, 14));
JScrollPane textPane = new JScrollPane(bitText);
Container contentPane = getContentPane();
contentPane.add(bitLabel, BorderLayout.NORTH);
contentPane.add(textPane, BorderLayout.CENTER);
setVisible(true);
}
// CarBitInfoView代码续
public void update(Observable subject) {
if ((subject == model) && (model.isBitBtnClicked())) {
// 从model获得数据
String sCar = model.getSelectedCar();
String pr = model.getBitPrice();
// 显示在视图上
bitText.append("\n Bit price for " + sCar + "=" + pr);
}
}
}// end CarBitInfoView