通過JAVA SWING看透MVC設計模式
通過JAVA SWING看透MVC設計模式<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />
一個好的使用者介面(GUI)的設計通常可以在現實世界找到相應的表現。例如,如果在您的面前擺放著一個類似於電腦鍵盤按鍵的一個簡單的按鈕,然而就是這麼簡單的一個按鈕,我們就可以看出一個GUI設計的規則,它由兩個主要的部分構成,一部分使得它具有了按鈕應該具有的動作特性,例如可以被按下。另外一部分則負責它的表現,例如這個按鈕是代表了A還是B。
看清楚這兩點你就發現了一個很強大的設計方法,這種方法鼓勵重用
如果您把上述設計思想應用到軟體開發領域,那麼取得相似的效果一點都不讓人驚奇。一個在軟體開發領域應用的非常廣泛的技術Model/View/Controller(MVC)便是這種思想的一個實現。
這當然很不錯,但是或許您又開始疑惑這和java基礎類JFC(Java Foundation Class)中的使用者介面設計部分(Swing)又有什麼關係呢?好的,我來告訴你。
儘管MVC設計模式通常是用來設計整個使用者介面
好,來吧,讓我來告訴你它是如何工作的。
MVC設計模式
就象我剛才指出的一樣,MVC設計模式把一個軟體元件區分為三個不同的部分,model,view,controller。
<?xml:namespace prefix = v ns = "urn:schemas-microsoft-com:vml" />
圖片1.MVC設計模式
Model是代表元件狀態和低階行為的部分,它管理著自己的狀態並且處理所有對狀態的操作,model自己本身並不知道使用自己的view和controller是誰,系統維護著它和view之間的關係,當model發生了改變系統還負責通知相應的view。
View代表了管理model所含有的資料的一個視覺上的呈現。一個Model可以有一個以上的View,但是Swing中卻很少有這樣的情況。
Controller管理著model和使用者之間的互動的控制。它提供了一些方法去處理當model的狀態發生了變化時的情況。
使用鍵盤上的按鈕的例子來說明一下:Model就是按鈕的整個機械裝置,View/Controller就是按鈕的表面部分。
下面的圖解釋瞭如何把一個JFC開發的使用者介面分為model,view,controller,注意,view/Controller被合併到了一起,這是MVC設計模式通常的用法,它們提供了元件的使用者介面(UI)。
圖2:JFC使用者介面元件
用Button的例子詳細說明
為了更好的理解MVC設計模式和Swing使用者介面元件之間的關係,讓我們更加深入的進行分析。我將採用最常見的元件button來說明。
我們從model來開始。
Model
一個按鈕的model所應該具備的行為由一個介面ButtonModel來完成。一個按鈕model例項封裝了其內部的狀態,並且定義了按鈕的行為。它的所有方法可以分為四類:
l查詢內部狀態
l操作內部狀態
l新增和刪除事件監聽器
l發生事件
其他的使用者介面元件有它們各自的與元件相關的Model,但是所有的元件Model都提供這四類方法。
View & Controller
上面的圖中講述一個按鈕的view/controller由一個介面ButtonUI完成。如果一個類實現了這個介面,那麼它將會負責建立一個使用者介面,處理使用者的操作。它的所有方法可以被分為三大類:
l繪製Paint
l返回幾何型別的資訊
l處理AWT事件
其他使用者介面元件有他們自己的元件相關的View/Controller,但是他們都提供上述三類方法。
程式設計師通常並不會直接和model以及view/controller打交道,他們通常隱藏於那些繼承自java.awt.Component的元件裡面了,這些元件就像膠水一樣把MVC三者合三為一。也正是由於這些繼承的元件物件,一個程式設計師可以很方便的混合使用Swing元件和AWT元件,然後,我們知道,Swing元件有很多都是直接繼承自相應的AWT元件,它能提供比AWT元件更加方便易用的功能,所以通常情況下,我們沒有必要混合使用兩者。
一個例項
現在我們已經明白了Java類與MVC各個部分的對應關係,我們可以更加深入一點去分析問題了。下面我們將要講述一個小型的使用MVC模式開發的例子。因為JFC十分的複雜,我只能把我的例子侷限於一個使用者介面元件裡面(如果你猜是一個按鈕的例子,那麼你對了!)
讓我們來看看這個例子的所有部分吧。
Button類
最顯而易見的開始的地方就是代表了按鈕元件本省的程式碼,因為這個類是大部分程式設計師會接觸的。
就像我前面提到的,按鈕使用者介面元件類實際上就是model和view/controller的之間的黏合劑。每個按鈕元件都和一個model以及一個controller關聯,model定義了按鈕的行為,而view/controller定義了按鈕的表現。而應用程式可以在任何事件改變這些關聯。讓我們看看得以實現此功能的程式碼。
public void setModel(ButtonModel buttonmodel)
{
if (this.buttonmodel != null)
{
this.buttonmodel.removeChangeListener(buttonchangelistener);
this.buttonmodel.removeActionListener(buttonactionlistener);
buttonchangelistener = null;
buttonactionlistener = null;
}
this.buttonmodel = buttonmodel;
if (this.buttonmodel != null)
{
buttonchangelistener = new ButtonChangeListener();
buttonactionlistener = new ButtonActionListener();
this.buttonmodel.addChangeListener(buttonchangelistener);
this.buttonmodel.addActionListener(buttonactionlistener);
}
updateButton();
}
public void setUI(ButtonUI buttonui)
{
if (this.buttonui != null)
{
this.buttonui.uninstallUI(this);
}
this.buttonui = buttonui;
if (this.buttonui != null)
{
this.buttonui.installUI(this);
}
updateButton();
}
public void updateButton()
{
invalidate();
}
在進入下一節之前,你應該多花一些時間來仔細閱讀一下Button類的原始碼。
ButtonModel類
ButtonModel維護著三種類型的狀態資訊:是否被按下(pressed),是否“武裝上了”(armed),是否被選擇(selected)。它們都是boolean型別的值。
一個按鈕被按下(pressed)是指當滑鼠在按鈕上面的時候,按下滑鼠但是還沒有鬆開滑鼠按鈕的狀態,及時使用者此時把滑鼠拖拽到按鈕的外面也沒有改變這種狀態。
一個按鈕是否“武裝了”(armed)是指按鈕被按下,並且滑鼠還在按鈕的上面。
一些按鈕還可能被選擇(selected),這種狀態通過重複的點選按鈕取得true或者false的值。
下面的程式碼是狀態pressed的一個預設的實現。狀態armed以及selected實現的程式碼與之類似。ButtonModel類應該被繼承,這樣可以覆蓋預設的狀態定義,實現有個性的按鈕。
private boolean boolPressed = false;
public boolean isPressed()
{
return boolPressed;
}
public void setPressed(boolean boolPressed)
{
this.boolPressed = boolPressed;
fireChangeEvent(new ChangeEvent(button));
}
按鈕的模型button model還負責通知其他物件(事件監聽器)它們所感興趣的事件。從下面的代買中我們可以看出當按鈕的轉檯發生改變的時候就會發出一個ChangeEvent。下面就是程式碼:
private Vector vectorChangeListeners = new Vector();
public void addChangeListener(ChangeListener changelistener)
{
vectorChangeListeners.addElement(changelistener);
}
public void removeChangeListener(ChangeListener changelistener)
{
vectorChangeListeners.removeElement(changelistener);
}
protected void fireChangeEvent(ChangeEvent changeevent)
{
Enumeration enumeration = vectorChangeListeners.elements();
while (enumeration.hasMoreElements())
{
ChangeListener changelistener =
(ChangeListener)enumeration.nextElement();
changelistener.stateChanged(changeevent);
}
}
在進入下一節之前,你應該多花一些時間來仔細閱讀一下ButtonModel類的原始碼。
ButtonUI類
按鈕的view/controller是負責構建表示層的。預設情況下它僅僅是用背景色畫一個矩形而已,他們的子類繼承了他們並且覆蓋了繪製的方法,使得按鈕可以有許多不同的表現,例如MOTIF,Windows 95,Java樣式等等。
public void update(Button button, Graphics graphics){
;
}
public void paint(Button button, Graphics graphics)
{
Dimension dimension = button.getSize();
Color color = button.getBackground();
graphics.setColor(color);
graphics.fillRect(0,0,dimension.width,dimension.height);
}
ButtonUI類並不自己處理AWT事件,他們會使用一個定製的事件監聽器把低階的AWT事件翻譯為高階的Button模型期望的語義事件。下面就是安裝/解除安裝事件監聽器的程式碼。
private static ButtonUIListener buttonuilistener = null;
public void installUI(Button button)
{
button.addMouseListener(buttonuilistener);
button.addMouseMotionListener(buttonuilistener);
button.addChangeListener(buttonuilistener);
}
public void uninstallUI(Button button)
{
button.removeMouseListener(buttonuilistener);
button.removeMouseMotionListener(buttonuilistener);
button.removeChangeListener(buttonuilistener);
}
View/Controller實際上就是一些方法。他們不維護任何自己的狀態資訊。因此,許多按鈕的例項可以共享一個ButtonUI例項。ButtonUI是通過在方面的引數列表裡面加上按鈕的引用來區分各個不同的按鈕。
同樣,希望你能多花一些時間來看看ButtonUI類,然後咱們進入下一節。
ButtonUIListener類
ButtonUIListener類可以幫助Button類去轉變滑鼠或者鍵盤的輸入為對按鈕模型的操作。這個監聽器類實現了:MouseListener,MouseMotionListener,ChangeListener介面,並且處理一下事件:
public void mouseDragged(MouseEvent mouseevent)
{
Button button = (Button)mouseevent.getSource();
ButtonModel buttonmodel = button.getModel();
if (buttonmodel.isPressed())
{
if (button.getUI().contains(button, mouseevent.getPoint()))
{
buttonmodel.setArmed(true);
}
else
{
buttonmodel.setArmed(false);
}
}
}
public void mousePressed(MouseEvent mouseevent)
{
Button button = (Button)mouseevent.getSource();
ButtonModel buttonmodel = button.getModel();
buttonmodel.setPressed(true);
buttonmodel.setArmed(true);
}
public void mouseReleased(MouseEvent mouseevent)
{
Button button = (Button)mouseevent.getSource();
ButtonModel buttonmodel = button.getModel();
buttonmodel.setPressed(false);
buttonmodel.setArmed(false);
}
public void stateChanged(ChangeEvent changeevent)
{
Button button = (Button)changeevent.getSource();
button.repaint();
}
在進入下一節之前希望你能仔細閱讀ButtonUIListener的原始碼。
總結
我希望你能按照上面講述的方法去做。如果不能,那麼所有的努力都將白費。這個例子以及Swing使用者介面元件的好處在於你不用去花時間去弄明白他們底層是如何設計實現的就可以很方便的使用他們了。他們都提供了預設的model以及view/controller,然後,當你自己做元件的時候,你會發現上面的思想的強大之處。
附件: