實(shí)戰(zhàn)MVC模式
- 作者: turbochen 2004年01月8日,星期四 12:40:49
MVC
“模型-視圖-控制器(Model-View-Controller,MVC)結(jié)構(gòu)是為那些需要為同樣的數(shù)據(jù)提供多個(gè)視圖的應(yīng)用程序而設(shè)計(jì)的,它很好的實(shí)現(xiàn)了數(shù)據(jù)層與表示層的分離。例如下圖中的例子:
我們看到,圖中的幾組數(shù)據(jù)以不同的形式(View)表現(xiàn)出來(lái),一個(gè)是表格樣式,一個(gè)是圖形樣式。
MVC通信方式
MVC把這種應(yīng)用程序分為三種對(duì)象類型:
- 模型:維護(hù)數(shù)據(jù)并提供數(shù)據(jù)訪問(wèn)方法。
- 視圖:給制模型的部分?jǐn)?shù)據(jù)或所有數(shù)據(jù)的可視圖。
- 控制器:處理事件.
以下是典型的MVC通信方式,
[{MVC_1.gif][MVC的通信方式]]
- 事件由控制器來(lái)處理,控制器接收用戶事件,并根據(jù)事件的類型來(lái)改變模型。
- 視圖事先會(huì)在模型中登記,當(dāng)模型數(shù)據(jù)發(fā)生改變時(shí),馬上通知已向此模型登記的每個(gè)視圖。
- 視圖從模取得最新的數(shù)據(jù)并刷新自己.
要實(shí)現(xiàn)MVC,最重要的一個(gè)環(huán)節(jié)是使用Design Pattern中的Observer模式。Observer模式允許某個(gè)對(duì)象在所觀察的對(duì)象發(fā)生修改時(shí)通知多個(gè)觀察者(Observer).
下面我們就以實(shí)例來(lái)講解如何用Obserer模式實(shí)現(xiàn)MVC的程序結(jié)構(gòu)。在我的例子中,我要實(shí)現(xiàn)一個(gè)學(xué)生年齡顯示的例子。分別用清單和圖形的方式顯示每個(gè)學(xué)生的年齡。當(dāng)年齡改變時(shí),自動(dòng)更新顯示。
Observer接口
為了實(shí)現(xiàn)觀察的對(duì)象發(fā)生修改時(shí)通知多個(gè)觀察者,通常要在被觀察者與觀察者之間有一個(gè)小的接口,如下:/* file: Observer.java */ public interface Observer { public void dataUpdate(Model model); }
這個(gè)接口中有一個(gè) dataUpdate(Model model)
方法,只要實(shí)現(xiàn)了這個(gè)接口對(duì)象,就成了一個(gè)觀察者。
模型Model
再來(lái)建立一個(gè)數(shù)據(jù)模型。在我的例子中,先建立了一個(gè)數(shù)據(jù)對(duì)象:
/* file: Data.java */ public class Data { public int value; // 學(xué)生年齡值 public String name; // 學(xué)生名 }
現(xiàn)在來(lái)建立一個(gè) Model
:
/* file: Model.java */ import java.util.*; public class Model { ArrayList data = new ArrayList(); ArrayList observer = new ArrayList(); public Model() { super(); } public Model(int[] value, String[] name) { for (int i = 0; i < value.length; i++) { addData(value[i], name[i]); } } public Model(Data[] data) { for (int i = 0; i < data.length; i++) { addData(data[i]); } } public void addData(int value, String name) { Data data = new Data(); data.value = value; data.name = name; this.data.add(data); } public void addData(Data data) { this.data.add(data); } public Data getData(int idx) { return (Data) (data.get(idx)); } public int size() { return data.size(); } // 用來(lái)向模型中登記觀察者. public void registerObserver(Observer o) { observer.add(o); } public void removeObserver(Observer o) { observer.remove(o); } // 當(dāng)數(shù)據(jù)改變時(shí),由Controller調(diào)用此方法,通知各個(gè)Observer,刷新視圖. public void changeModel(Model model) { data.clear(); for (int i = 0; i < model.size(); i++) { this.addData(model.getData(i)); } dataUpdate(); } private void dataUpdate() { for (Iterator i = observer.iterator(); i.hasNext();) { Observer o = (Observer) (i.next()); o.dataUpdate(this); } } }
這個(gè)模型提供各種數(shù)據(jù)訪問(wèn)的方法。并提供一個(gè) changeModel(Model model)
方法供 Controller
訪問(wèn)。還提供一個(gè) registerObserver(Observer o)
方法,用來(lái)向 Model
中登記觀察者 Observer
。
視圖View
我們要實(shí)現(xiàn)一個(gè)清單顯示樣式的視圖View1
和一個(gè)圖形方式顯示的視圖 View2
,并讓它們實(shí)現(xiàn) Observer
接口,以便當(dāng) Model
數(shù)據(jù)改變時(shí),自動(dòng)刷新自己.
/* file: View1.java */ import javax.swing.*; import java.awt.*; import javax.swing.border.*; public class View1 extends JPanel implements Observer { Model model; public View1() { } public View1(Model model) { try { this.model = model; jbInit(); } catch (Exception e) { e.printStackTrace(); } } private void jbInit() throws Exception { this.setBackground(Color.white); this.setBorder(new TitledBorder(BorderFactory.createLineBorder( Color.black, 1), "View1")); } public void paintComponent(Graphics g) { super.paintComponent(g); if (model == null) return; int x = 20, y = 50; int h = g.getFontMetrics().getHeight(); for (int i = 0; i < model.size(); i++) { Data data = model.getData(i); g.drawString(data.name, x, y); y += h; g.drawString(String.valueOf(data.value), x, y); y += h; } } // 當(dāng)模型數(shù)據(jù)發(fā)生改變時(shí),會(huì)自動(dòng)調(diào)用此方法來(lái)刷新圖形 public void dataUpdate(Model model) { /** @todo: Implement this Observer method */ this.model = model; repaint(); } }
View2.java
/* file: View2.java */ import javax.swing.*; import java.awt.*; import javax.swing.border.*; public class View2 extends JPanel implements Observer { Model model; public View2() { } public View2(Model model) { try { this.model = model; jbInit(); } catch (Exception e) { e.printStackTrace(); } } private void jbInit() throws Exception { this.setBackground(Color.white); this.setBorder(new TitledBorder(BorderFactory.createLineBorder( Color.black, 1), "View1")); } public void paintComponent(Graphics g) { super.paintComponent(g); if (model == null) return; int x = 20, y = 50; int h = g.getFontMetrics().getHeight(); int width = this.getWidth(); int height = this.getHeight(); int sy = height / model.size(); int sx = width / 2; for (int i = 0; i < model.size(); i++) { Data data = model.getData(i); int value = data.value; int dx = 3; int r = 3; Color c = new Color((int) (255 * Math.random()), (int) (255 * Math .random()), (int) (255 * Math.random())); int cx = sx; int cy = y + i * sy; for (int j = 0; j < value; j++) { g.setColor(c); g.drawOval(cx, cy, r, r); r += dx; } g.drawString(data.name, 25, cy); } } // 當(dāng)模型數(shù)據(jù)發(fā)生改變時(shí),會(huì)自動(dòng)調(diào)用此方法來(lái)刷新圖形 public void dataUpdate(Model model) { /** @todo: Implement this Observer method */ this.model = model; repaint(); } }
控制器Controller
好了,MVC中的Model
, Observer
都建立好了,我們最后來(lái)做一個(gè) Controller
:
import java.awt.*; import javax.swing.*; import java.awt.event.*; public class Controller extends JFrame { Model model = new Model(); View1 view1 = new View1(model); View2 view2 = new View2(model); JScrollPane jScrollPane1 = new JScrollPane(); JButton jButton1 = new JButton(); JTextField jTextField1 = new JTextField(); JTextField jTextField2 = new JTextField(); JLabel jLabel1 = new JLabel(); JLabel jLabel2 = new JLabel(); JLabel jLabel3 = new JLabel(); public Controller() { try { jbInit(); } catch (Exception e) { e.printStackTrace(); } } private void jbInit() throws Exception { Data[] data = new Data[2]; data[0] = new Data(); data[1] = new Data(); data[0].name = "Ted"; data[0].value = 20; data[1].name = "Joy"; data[1].value = 14; model.addData(data[0]); model.addData(data[1]); // 注意下面兩行:向模型中登記它的觀察者View1和View2. model.registerObserver(view1); model.registerObserver(view2); this.getContentPane().setLayout(null); jScrollPane1.setBounds(new Rectangle(0, 0, 3, 3)); jButton1.setBounds(new Rectangle(309, 259, 101, 27)); jButton1.setText("Update"); jButton1.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(ActionEvent e) { jButton1_actionPerformed(e); } }); jTextField1.setText("20"); jTextField1.setBounds(new Rectangle(80, 254, 52, 30)); jTextField2.setText("14"); jTextField2.setBounds(new Rectangle(178, 255, 50, 31)); jLabel1.setText("Age:"); jLabel1.setBounds(new Rectangle(41, 226, 47, 23)); jLabel2.setText("Ted"); jLabel2.setBounds(new Rectangle(42, 252, 35, 33)); jLabel3.setText("Joy"); jLabel3.setBounds(new Rectangle(144, 255, 31, 31)); view1.setBounds(new Rectangle(7, 5, 225, 208)); view2.setBounds(new Rectangle(234, 4, 219, 209)); this.getContentPane().add(jScrollPane1, null); this.getContentPane().add(jTextField2, null); this.getContentPane().add(jTextField1, null); this.getContentPane().add(jLabel2, null); this.getContentPane().add(jLabel3, null); this.getContentPane().add(jLabel1, null); this.getContentPane().add(jButton1, null); this.getContentPane().add(view1, null); this.getContentPane().add(view2, null); } // 按下Update按鈕,通知Model數(shù)據(jù)發(fā)生改變. void jButton1_actionPerformed(ActionEvent e) { Data[] data = new Data[2]; data[0] = new Data(); data[1] = new Data(); data[0].name = jLabel1.getText(); data[0].value = Integer.parseInt(jTextField1.getText()); data[1].name = jLabel2.getText(); data[1].value = Integer.parseInt(jTextField2.getText()); Model m = new Model(data); this.model.changeModel(m); } public static void main(String[] args) { Controller c = new Controller(); c.setSize(475, 310); c.setVisible(true); } }
運(yùn)行程序
各位可以將這些代碼各自存為相應(yīng)的源文件,執(zhí)行以下命令編譯
javac Controller.java
運(yùn)行
java Controller.class
就可以看到程序執(zhí)行的效果,
你可以試著改變兩個(gè)學(xué)生的年齡,按一下 Update 按鈕,相應(yīng)的視圖就更新了。怎么樣,體驗(yàn)到了MVC結(jié)構(gòu)給程序帶來(lái)的方便性了吧?;)
大家如果運(yùn)行程序有任何問(wèn)題,或需編譯好的程序,可與我聯(lián)絡(luò)。
關(guān)于作者
陳先波(turbochen)是賽微科技公司(www.qissoft.com)的Java開(kāi)發(fā)員,曾為多個(gè)技術(shù)網(wǎng)站撰稿。有超過(guò)三年的Java開(kāi)發(fā)經(jīng)驗(yàn),主要致力于Java技術(shù)在質(zhì)量管理信息系統(tǒng)中的應(yīng)用,請(qǐng)通過(guò)turbochen@163.com與作者聯(lián)系。</script>