再戰MVC(-)

前言

之所以說是再戰,是因爲在沒有學習設計模式之前已經基于MVC體系結構做過一些項目,主要是小項目,當初理解MVC有一些困難。現在已經把Gof所說的相對簡單但是最常見的這些設計模式:

Abstract Factory

Adapter

Composite

Decorator

Factory Method

Observer

Strategy

Template Method

都詳細過了一遍了,而我們都知道MVC是一種比較特殊的模式,MVC並不屬于GOF的23個設計模式之列,但是Smalltalk的MVC框架結構在著名的GOF書中作爲一個重要的例子被提出來,並給予了很高的評價。一般的來講,我們認爲GOF的23個模式是一些中級的模式,在它下面還可以抽象出一些更爲一般的低層的模式,在其上也可以通過組合來得到一些高級的模式,即架構模式。MVC就可以看作是一些模式進行組合之後的結果(實際上,MVC的出現要早于設計模式的提出,因而只是對它在設計模式的基礎上進行在分析)。所以說現在有必要站在設計模式的思想上來看MVC模式。<<Java與模式>>在MVC模式與用戶輸入數據檢查這個專題裏大量地談到MVC模式跟設計模式的關系,本文按這個專題的思路以筆記的形式展開。

MVC模式可以分解爲以下設計模式

在GOF書的Introduction中,有一小節是“Design Patterns in Smalltalk MVC”即介紹在MVC模式裏用到的設計模式。它大概向我們傳達了這樣的信息:合成模式+策略模式+觀察者模式約等于MVC模式(當然MVC模式要多一些東西)。也就是說它在大部分情況下是下面幾個模式:

1觀察者模式

類圖結構在Gof裏的表示如下:

王朝网络

2 合成模式

類圖結構在Gof裏的表示如下:

王朝网络

3策略模式

王朝网络

除了這三種主要的設計模式,Gof最後也提到了MVC還使用了其它的設計模式,比如說工廠方法模式用來說明對一個view的默認的控制器,還有裝飾模式用來爲view增加一個滾動條等。但是在MVC模式裏起主要作用的還是前面列出來的三個設計模式。

模式的分類

書中提到由于[GOF95]是論述軟件模式的著作的第一本,也是OO設計理論著作中最流行的一本,因此有些人常常使用設計模式(Design Pattern)一詞來指所有直接處理軟件架構,設計,程序實現的任何種類的模式。另外一種說法是強調要劃分三種不同層次的模式:架構模式(Architectual Pattern),設計模式(Design Pattern),成例(Idiom)。成例有時稱爲代碼模式(Codeing Pattern)。

架構模式(Architectual Pattern)

面向對象軟件大師Martin Fowler有本書叫做<< Patterns of Enterprise Application Architecture>>中文名叫<<企業應用架構模式>>。這本書就是關于如何將企業應用分層,以及如何組織各層工作的。例如這本書在第二部分深入講解模式時,在談到了Web表示層的時候就分析了MVC模式,從這些我們可以知道架構模式提供一些事先定義好的子系統,指定他們的責任,並給出把它們組織在一起的法則和指南,一個架構模式常常可以分解成很多個設計模式的聯合使用。

設計模式

設計模式描述在軟件設計過程中普遍存在相互通信的組件中重複出現的結構,這種結構解決在一定的背景中的具有一般性的設計問題。

代碼模式或成例(Coding Pattern或Idiom)

代碼模式(或成例)是較低層次的模式,並與編程語言密切相關。代碼模式描述怎樣利用一個特定的編程語言的特點來實現一個組件的某些特定的方面或關系。

從上面的敘述中其實我們不難從一個軟件系統中找到他們,因爲三種不同的模式存在于它們自的抽象層次和具體層次上,所以少了其中任何一種模式,或者有一種設計的不好,都會給整個軟件系統帶來害處,如可維護性低等缺點。因爲架構模式是一個系統的高層次策略,涉及到大尺度的組件以及整體性質和力學,它設計的好壞可以影響到總體布局和框架性結構。設計模式是中等尺度的結構策略。這些中等尺度的結構實現了一些大尺度組件的行爲和它們之間的關系,所以設計模式的好壞不會影響到系統的總休布局和總體框架,但是它會影響到系統的擴展性,維護性等方面。代碼模式是特定的範例和與特定編程語言有關的編程技巧。它不會影響到一個部件或子系統的中等尺度的結構,更不會影響到系統的總體布局和大尺度框架,但是會影響到一個中等尺度組件的內部,外部的結構或行爲的底層細節。

談MVC架構模式

MVC中的三個角色

王朝网络

Model(模型端)

Mod封裝的是數據源和所有基于對這些數據的操作。在一個組件中,Model往往表示組件的狀態和操作這些狀態的方法,往往是一系列的公開方法。通過這些公開方法,便可以取得模型端的所有功能。

在這些公開方法中,有些是取值方法,讓系統其他部分可以得到模型端的內部狀態參數,其他的改值方法則允許外部修改模型端的內部狀態。模型端還必須有方法登記視圖,以便在模型端的內部狀態發生變化時,可以通知視圖端。我們可以自己定義一個Subject接口來提供登記和通知視圖所需的接口或者繼承Java.util.Observable類,讓父類完成這件事。

多個View(視圖端)

View封裝的是對數據源Model的一種顯示。一個模型可以由多個視圖,並且可以在需要的時候動態地登記上所需的視圖。而一個視圖理論上也可以同不同的模型關聯起來。

在前言裏提到了,MVC模式用到了合成模式,這是因爲在視圖端裏,視圖可以嵌套,比如說在一個JFrame組件裏面,可以有菜單組件,很多按鈕組件等。

多個Controller(控制器端)

封裝的是外界作用于模型的操作。通常,這些操作會轉發到模型上,並調用模型中相應的一個或者多個方法(這個方法就是前面在介紹模型的時候說的改值方法)。一般Controller在Model和View之間起到了溝通的作用,處理用戶在View上的輸入,並轉發給Model來更改其狀態值。這樣Model和View兩者之間可以做到松散耦合,甚至可以彼此不知道對方,而由Controller連接起這兩個部分。也在前言裏提到,MVC用到了策略模式,這是因爲View用一個特定的Controller的實例來實現一個特定的響應策略,更換不同的Controller,可以改變View對用戶輸入的響應。

MVC模式用在基于C/S的應用

盡管MVC設計模式通常是用來設計整個用戶界面(GUI)的,JFC的設計者們卻獨創性的把這種設計模式用來設計Swing中的單個的組件(Component),例如表格Jtable等。下面的討論也就是對于Swing單個組件所體現出來的這種MVC模式來說的。

嚴格地講,Swing中的MVC實際上是MVC的一個變體:M-VC。 拿JTable來說,MVC 就是 TableModel JTable TableUI. Swing中只顯示的定義了Model接口(TableModel),而在一個UI對象中集成了視圖和控制器的部分機制(JTable)。View和Control比較松散的交叉組合在一起,而更多的控制邏輯是在事件監聽者部分引入的爲了更好地展現Swing是一個設計優秀的Java包,充滿了大師的智慧,讓我們更加深入的進行分析,我將采用最常見的組件button來說明。

注:以下例子是摘自網上一篇名爲“通過Java Swing看透MVC設計模式”的佚名文章,感覺對從Swing庫的設計來看MVC模式的應用有相當大的幫助,故插到此處同大家分享,黑色字迹部分爲自己的說明。

(轉載開始)

Model的設計

一個按鈕的model所應該具備的行爲由一個接口ButtonModel來完成。一個按鈕model實例封裝了其內部的狀態,並且定義了按鈕的行爲。它的所有方法可以分爲四類:

1、查詢內部狀態

2、操作內部狀態

3、添加和刪除事件監聽器

4、發生事件

程序員通常並不會直接和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定義了按鈕的表現。而應用程序可以在任何事件改變這些關聯。讓我們看看得以實現此功能的代碼。

注:以下代碼是我從JDK1.4源代碼AbstractButton類中提取出來的,因爲查看Jbutton源代碼(再次提供JDK1.4在線源代碼地址)我們可以發現,它是繼承自AbstractButton類的,所以下面的代碼是來自AbstractButton的,跟原文有點不一樣:

1. /**

2.

3. * Sets the model that this button represents.

4.

5. * @param m the new <code>ButtonModel</code>

6.

7. * @see #getModel

8.

9. * @beaninfo

10.

11. * bound: true

12.

13. * description: Model that the Button uses.

14.

15. */

16.

17. public void setModel(ButtonModel newModel) {

18.

19.

20.

21. ButtonModel oldModel = getModel();

22.

23.

24.

25. if (oldModel != null) {

26.

27. oldModel.removeChangeListener(changeListener);

28.

29. oldModel.removeActionListener(actionListener);

30.

31. changeListener = null;

32.

33. actionListener = null;

34.

35. }

36.

37.

38.

39. model = newModel;

40.

41.

42.

43. if (newModel != null) {

44.

45. changeListener = createChangeListener();

46.

47. actionListener = createActionListener();

48.

49. itemListener = createItemListener();

50.

51. newModel.addChangeListener(changeListener);

52.

53. newModel.addActionListener(actionListener);

54.

55. newModel.addItemListener(itemListener);

56.

57. mnemonic = newModel.getMnemonic();

58.

59. } else {

60.

61. mnemonic = '\0';

62.

63. }

64.

65.

66.

67. updateDisplayedMnemonicIndex(getText(), mnemonic);

68.

69.

70.

71. firePropertyChange(MODEL_CHANGED_PROPERTY, oldModel, newModel);

72.

73. if (newModel != oldModel) {

74.

75. revalidate();

76.

77. repaint();

78.

79. }

80.

81. }

注:你可以多花一些時間按我上面提供的鏈接地址來仔細閱讀一下Button類及其它相關類的源代碼。

ButtonModel類

ButtonModel維護著三種類型的狀態信息:是否被按下(pressed),是否“武裝上了”(armed),是否被選擇(selected)。它們都是boolean類型的值。

一個按鈕被按下(pressed)是指當鼠標在按鈕上面的時候,按下鼠標但是還沒有松開鼠標按鈕的狀態,即使用戶此時把鼠標拖拽到按鈕的外面也沒有改變這種狀態。

一個按鈕是否“武裝了”(armed)是指按鈕被按下,並且鼠標還在按鈕的上面。

一些按鈕還可能被選擇(selected),這種狀態通過重複的點擊按鈕取得true或者false的值。

注:查看ButtonModel源代碼我們可以看到它是一個接口,定義了一組方法:

public interface ButtonModel extends ItemSelectable {

boolean isArmed();

boolean isPressed();

boolean isRollover();

void addActionListener(ActionListener l);

void removeActionListener(ActionListener l);

void addItemListener(ItemListener l);

void removeChangeListener(ChangeListener l);

其實看到這裏,如果你再回頭看下這個例子剛開始時“Model的設計”以及前面的內容,就會明白什麽是“Mod封裝的是數據源和所有基于對這些數據的操作。在一個組件中,Model往往表示組件的狀態和操作這些狀態的方法,往往是一系列的公開方法。通過這些公開方法,便可以取得模型端的所有功能”

我們再來看看AbstractButton的源代碼:

1. public abstract classimplements ItemSelectable, SwingConstants { AbstractButton extends JComponent

2. protected ButtonModel model= null;

3. protected ChangeListener changeListener = null;

4. protected ActionListener actionListener = null;

5. protected ItemListener itemListener = null;

6. protected transient ChangeEvent changeEvent;

7. public boolean isSelected() {

8. return model.isSelected();

9. }

10.public void doClick() {

11.doClick(68);

12. }

13.public void doClick(int pressTime) {

14. Dimension size = getSize();

15.model.setArmed(true);

16. model.setPressed(true);

17. paintImmediately(new Rectangle(0,0, size.width, size.height));

18.try {

19.Thread.currentThread().sleep(pressTime);

20. } catch(InterruptedException ie) {

21.}

22.model.setPressed(false);

23.model.setArmed(false);

24.}

25.//其它代碼

26.}

上面的代碼setArmed等方法是由DefaultButtonModel定義的,它是ButtonModel接口的一個默認實現。我們如果繼承了DefaultButtonModel,這樣可以覆蓋缺省的狀態定義,實現有個性的按鈕。ButtonUI類

按鈕的view/controller是負責構建表示層的。缺省情況下它僅僅是用背景色畫一個矩形而已,他們的子類繼承了他們並且覆蓋了繪制的方法,使得按鈕可以有許多不同的表現,例如MOTIF,Windows 95,Java樣式等等。

注:在javax.swing.plaf這個包裏我們可以找到ButtonUI,這是一個抽象類,類內部是空的,而在javax.swing.plaf.basic這個包裏我們可以找到BasicButtonUI,它繼承自ButtonUI,其實我們觀察下包結構會發現有javax.swing.plaf.metal,javax.swing.plaf.multi等,這幾個就是來控制Button在不同系統間的不同的顯示效果。:

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 uninstallUI(Button button)

{

button.removeMouseListener(buttonuilistener);

button.removeMouseMotionListener(buttonuilistener);

button.removeChangeListener(buttonuilistener);

}

View/Controller實際上就是一些方法。他們不維護任何自己的狀態信息。因此,許多按鈕的實例可以共享一個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();

}

(轉載完)

通過這些相信大家能夠感受到Swing的設計是MVC的典範,這是一點都不假的。下一節將要看看在基于B/S的應用中MVC模式是如何被應用的。

再戰MVC(二)
MVC模式在J2EE技術中的應用  MVC模式並不能自動保證一個結構設計是正確的,如何在一個系統的設計中正確地使用MVC架構模式與系統使用的技術有密切的關系。2EE中有幾個核心的技術:JSP,JavaBean,Servlet,EJB,Ses...查看完整版>>再戰MVC(二)
 
年度最強屏幕PK 酷派N930再戰iPhone 4
年度最強屏幕PK 酷派N930再戰iPhone 4
隨著手機技術的不斷研發,系統不斷提升,手機性能也是越來越強大。爲了追求完美,越來越多的知名廠商開始將目光轉移到了手機屏幕的用料上。在WWDC 2010蘋果發布會上,喬布斯就將iPhone 4的屏幕拿出來炫耀一番,相對于...查看完整版>>年度最強屏幕PK 酷派N930再戰iPhone 4
 
14年後人機再戰首日:IBM華生累積獎金暫時領先
14年後人機再戰首日:IBM華生累積獎金暫時領先
文章分類: 科技新聞 雖然這幾天被衆多的MWC新聞塞爆,不過另一場受到注目的國際焦點,就是IBM新一代超級計算機"華生(Watson)"與人類的機智問答大戰,而在面對曾拿下節目最高累積獎金的Brad Rutter以及衛冕最久的K...查看完整版>>14年後人機再戰首日:IBM華生累積獎金暫時領先
 
Computex 2010:聽不累耳機再戰 Computex
Computex 2010:聽不累耳機再戰 Computex
聽不累耳機參展 Computex 第三年,卻已經是連續二年進入 Computex Best Choice Award 2010 的廠商,真的相當值得鼓勵呢!新一代的聽不累耳機新增的幾項優點包括內耳擴音的部份增加了小突出,讓耳機更不容易掉落。耳機...查看完整版>>Computex 2010:聽不累耳機再戰 Computex
 
畫質蛻變再戰NC 實力派單反索尼α550評測
畫質蛻變再戰NC 實力派單反索尼α550評測
·定位入門、劍指中端在年中索尼發布α230和α330的時候,很多人都疑惑,難道這就是佳能500D的對手?顯然,老道的索尼在入門單反市場藏有後招,筆者手中的α550就是今年秋天索尼單反真正的“實力之作”。高像素Exmor...查看完整版>>畫質蛻變再戰NC 實力派單反索尼α550評測
 
再戰6630之MP3亂碼解決
再戰6630之MP3亂碼解決
相信很多用6630或是S60系列機子的機油都有被MP3亂碼困擾的經曆,這對于熟識S60的玩家來說是小菜一碟很容易解決,但大部分初級玩家都會被這個小問題困擾,要知道愛機的朋友大多是不能接受這種小瑕疵的。現在本人就通過...查看完整版>>再戰6630之MP3亂碼解決
 
MVC真的適合PHP麽?
摘要:近來設計模式風行,MVC隨處可見,PHP領域也不例外,很多論壇都開始討論在PHP中使用MVC。然而,M、V、C在PHP中到底該如何實現?MVC真的適合PHP麽?這裏Easy給出一些自己的思考。近來設計模式風行,MVC隨處可見,...查看完整版>>MVC真的適合PHP麽?
 
IBM再戰電力寬帶市場 擬在美國再建BPL網絡
  近日,IBM公司計劃于美國東部再建一條寬帶電力線(BPL)網絡。   據國外媒體報道,IBM計劃在美國東部地區建立BPL網絡以提供互聯網服務。該網絡成功建立後,會由IBPC公司全權負責相關的網絡事宜。   BPL網絡使用...查看完整版>>IBM再戰電力寬帶市場 擬在美國再建BPL網絡
 
[實話實說]八代雅閣 “黑白雙煞”再戰車市
熱點話題:單雙號民意大調查 全新的第八代雅閣(參數 論壇 圖片)在外形設計上更爲大氣、豪華,其中最爲明顯的就是車頭部分。其流線型設計的側身,車身融合了空氣動力學和藝術美學的精粹,雕塑般的腰...查看完整版>>[實話實說]八代雅閣 “黑白雙煞”再戰車市
 
 
回到王朝網路移動版首頁