MVC模式淺談
一、MVC模式概述
模型-視圖-控制器(MVC模式)是一種非常經(jīng)典的軟件架構(gòu)模式,在UI框架和UI設(shè)計(jì)思路中扮演著非常重要的角色。從設(shè)計(jì)模式的角度來看,MVC模式是一種復(fù)合模式,它將多個(gè)設(shè)計(jì)模式在一種解決方案中結(jié)合起來,用來解決許多設(shè)計(jì)問題。MVC模式把用戶界面交互分拆到不同的三種角色中,使應(yīng)用程序被分成三個(gè)核心部件:Model(模型)、View(視圖)、Control(控制器)。它們各自處理自己的任務(wù):
(1)模型:模型持有所有的數(shù)據(jù)、狀態(tài)和程序邏輯。模型獨(dú)立于視圖和控制器。
(2)視圖:用來呈現(xiàn)模型。視圖通常直接從模型中取得它需要顯示的狀態(tài)與數(shù)據(jù)。對(duì)于相同的信息可以有多個(gè)不同的顯示形式或視圖。
(3)控制器:位于視圖和模型中間,負(fù)責(zé)接受用戶的輸入,將輸入進(jìn)行解析并反饋給模型,通常一個(gè)視圖具有一個(gè)控制器。
MVC模式將它們分離以提高系統(tǒng)的靈活性和復(fù)用性,不使用MVC模式,用戶界面設(shè)計(jì)往往將這些對(duì)象混在一起。MVC模式實(shí)現(xiàn)了模型和視圖的分離,這帶來了幾個(gè)好處。
(1)一個(gè)模型提供不同的多個(gè)視圖表現(xiàn)形式,也能夠?yàn)橐粋€(gè)模型創(chuàng)建新的視圖而無須重寫模型。一旦模型的數(shù)據(jù)發(fā)生變化,模型將通知有關(guān)的視圖,每個(gè)視圖相應(yīng)地刷新自己。
(2)模型可復(fù)用。因?yàn)槟P褪仟?dú)立于視圖的,所以可以把一個(gè)模型獨(dú)立地移植到新的平臺(tái)工作。
(3)提高開發(fā)效率。在開發(fā)界面顯示部分時(shí),你僅僅需要考慮的是如何布局一個(gè)好的用戶界面;開發(fā)模型時(shí),你僅僅要考慮的是業(yè)務(wù)邏輯和數(shù)據(jù)維護(hù),這樣能使開發(fā)者專注于某一方面的開發(fā),提高開發(fā)效率。
圖1.1MVC模式結(jié)構(gòu)圖
如圖1.1所示,視圖中用戶的輸入被控制器解析后,控制器改變狀態(tài)激活模型,模型根據(jù)業(yè)務(wù)邏輯維護(hù)數(shù)據(jù),并通知視圖數(shù)據(jù)發(fā)生變化,視圖得到通知后從模型中獲取數(shù)據(jù)刷新自己。
二、深入解析MVC模式
對(duì)MVC模式有了一個(gè)初步的認(rèn)識(shí)之后,我們可以繼續(xù)深入地了解它。MVC模式的關(guān)鍵是實(shí)現(xiàn)了視圖和模型的分離。這是如何實(shí)現(xiàn)的呢?MVC模式通過建立一個(gè)“發(fā)布/訂閱”(publish-subscribe)的機(jī)制來分離視圖和模型。發(fā)布-訂閱(publish-subscribe)機(jī)制的目標(biāo)是發(fā)布者,它發(fā)出通知時(shí)并不需知道誰是它的觀察者。可以有任意數(shù)目的觀察者訂閱并接收通知。MVC模式最重要的是用到了Observer(觀察者模式),正是觀察者模式實(shí)現(xiàn)了發(fā)布-訂閱(publish-subscribe)機(jī)制,實(shí)現(xiàn)了視圖和模型的分離。因此談到MVC模式就必須談到觀察者模式。如圖2.1所示。
圖2.1 觀察者模式
觀察者模式:定義對(duì)象間的一種一對(duì)多的依賴關(guān)系,當(dāng)一個(gè)對(duì)象的狀態(tài)發(fā)生改變時(shí),所有依賴于它的對(duì)象都得到通知并被自動(dòng)更新。
圖2.1中Subject我們稱為主題,Observer稱為觀察者。主題提供注冊(cè)觀察者、移除觀察者和通知觀察者的接口,這樣只要觀察者注冊(cè)成為主題的一個(gè)觀察者的話,主題在狀態(tài)發(fā)生變化時(shí)會(huì)通知觀察者。觀察者有一個(gè)更新自己的接口,當(dāng)收到主題的通知之后觀察者就會(huì)調(diào)用該接口更新自己。如何實(shí)現(xiàn)注冊(cè)和通知的呢?如果是用C++或java的話,主題就需要有一個(gè)觀察者鏈表,注冊(cè)就是將觀察者加入到該鏈表中,移除則是從該鏈表中刪除,當(dāng)主題狀態(tài)變化時(shí)就遍歷該鏈表所有的觀察者通知它們更新自己。在c#中可以通過委托實(shí)現(xiàn)注冊(cè)。
觀察者模式中的主題就對(duì)應(yīng)于MVC模式中Model(模型),觀察者就對(duì)應(yīng)于MVC模式中的View(視圖)。視圖向模型注冊(cè)成為觀察者,模型(主題)變化時(shí)就通知視圖(觀察者)更新自己,但是還有一個(gè)問題,我們?nèi)绻灰肟刂破鞯脑?,直接將接受用戶輸入并解析輸入操縱模型的功能放到視圖中的話會(huì)產(chǎn)生兩個(gè)問題:第一、會(huì)造成視圖代碼變得復(fù)雜,使得視圖就有了兩個(gè)責(zé)任,不但要管理用戶界面,還要處理如何控制模型的邏輯,有違單一責(zé)任的設(shè)計(jì)原則,一個(gè)類應(yīng)該僅有一個(gè)引起它變化的原因,如果一個(gè)類承擔(dān)的責(zé)任過多,就等于把這些責(zé)任耦合在一起,一個(gè)責(zé)任的變化可能會(huì)削弱或抑制這個(gè)類完成其他責(zé)任的能力,這種耦合會(huì)導(dǎo)致脆弱的設(shè)計(jì),當(dāng)變化同時(shí)面臨兩個(gè)或多個(gè)方向變化時(shí)設(shè)計(jì)會(huì)遭到意想不到的破壞甚至根本沒辦法處理。第二、會(huì)造成模型和視圖的緊耦合,如果你想復(fù)用此視圖來處理其他模型,根本不可能。于是把控制器從視圖中分離出來,將視圖和模型解耦,通過控制器來保持控制器和視圖之間的松耦合,使設(shè)計(jì)更有彈性和容易擴(kuò)展,足以容納以后的改變。
控制器相當(dāng)于是視圖的行為,我們還要考慮到今后可能面臨的變化,例如視圖想換一種行為,我們是否做好了應(yīng)付這種變化的準(zhǔn)備呢?我們不應(yīng)該將視圖和控制器緊耦合,例如不要一開始就將視圖和某一個(gè)具體行為綁定起來,這樣會(huì)使視圖更換行為的時(shí)候變得很困難,我們應(yīng)該能動(dòng)態(tài)的給視圖指定一個(gè)行為,用多態(tài)使視圖和控制器之間松耦合,可以使視圖輕松地更換行為,于是策略模式登場(chǎng)了,策略模式正是用來解決這個(gè)問題的。如圖2.2所示。
策略模式:定義了算法族,分別封裝起來,讓他們之間可以相互替換,此模式讓算法的變化獨(dú)立于使用算法的客戶。
MVC模式視圖和控制器實(shí)現(xiàn)了經(jīng)典的策略模式:視圖是一個(gè)對(duì)象,可以被調(diào)整使用不同的策略(行為),而控制器提供了策略(行為)。視圖想換另一種行為,換控制器就可以了。視圖只關(guān)心系統(tǒng)中可視的部分,對(duì)于任何界面行為,都委托給控制器處理。使用策略模式也可以讓視圖和模型之間的關(guān)系解耦,因?yàn)榭刂破髫?fù)責(zé)和模型交互來傳遞用戶的請(qǐng)求。對(duì)于工作是怎么完成的,視圖毫不知情。
圖2.2策略模式
三、MVC模式的應(yīng)用
GOF四人組提出MVC模式的主要關(guān)系是由Observer(觀察者模式)、Composite(組合模式)和Strategy(策略模式)三個(gè)設(shè)計(jì)模式給出的。當(dāng)然其中還可能使用了其他的設(shè)計(jì)模式,這要根據(jù)具體場(chǎng)景的需要來決定。GOF四人組提出復(fù)雜的視圖可以根據(jù)實(shí)際需要用組合模式來實(shí)現(xiàn),當(dāng)然,也要注意避免過度設(shè)計(jì),如果視圖的結(jié)構(gòu)不復(fù)雜就沒必要采用組合模式了。我們和一個(gè)同事一起開發(fā)的項(xiàng)目中,UI框架的設(shè)計(jì)我采用了MVC模式,主要結(jié)合了Observer(觀察者模式)、Strategy(策略模式)、Command(命令模式)和Singleton(單件模式)。用戶界面不太復(fù)雜,因此視圖不需要應(yīng)用組合模式,在界面的框架設(shè)計(jì)中,界面菜單、對(duì)話框、樹形控件和表格控件對(duì)應(yīng)于MVC模式中的View(視圖),其中對(duì)話框采用了單件模式,保證一個(gè)類僅有一個(gè)對(duì)話框?qū)嵗⑻峁┮粋€(gè)訪問它的全局訪問點(diǎn)??刂破魑挥谝晥D和命令對(duì)象或模型中間,將界面請(qǐng)求封裝成一個(gè)命令對(duì)象,命令對(duì)象執(zhí)行控制器發(fā)送過來的命令,根據(jù)命令對(duì)象模型負(fù)責(zé)處理邏輯和數(shù)據(jù)。項(xiàng)目UI框架中采用Observer(觀察者模式)和Strategy(策略模式)使視圖和模型分離,并讓視圖能靈活地更換行為,在前面已有詳述,不再贅述,我要重點(diǎn)提的是我為什么要采用Command(命令模式)以及如何將命令模式嵌入到MVC模式當(dāng)中。如圖3.1所示。
圖3.1 命令模式
命令模式:將一個(gè)請(qǐng)求封裝為一個(gè)對(duì)象,從而使你可用不同的請(qǐng)求對(duì)客戶進(jìn)行參數(shù)化;對(duì)請(qǐng)求排隊(duì)或記錄請(qǐng)求日志,以及支持可撤消的操作。
根據(jù)項(xiàng)目的需求:用戶可以通過工具條按鈕、菜單按鈕、對(duì)話框按鈕或鼠標(biāo)右鍵等操作來執(zhí)行同一項(xiàng)請(qǐng)求;支持取消和重做操作;很容易增加或更換新的命令請(qǐng)求。這些問題都可以通過命令模式來解決。命令模式可以使不同地方的按鈕或操作代表同一項(xiàng)功能,只需要讓它們共享響應(yīng)具體Command子類的同一實(shí)例即可。還可以通過多態(tài)動(dòng)態(tài)替換Command對(duì)象從而輕松地更換請(qǐng)求命令。Command的Execute操作可在實(shí)施操作前將狀態(tài)存儲(chǔ)起來,在取消操作時(shí)這個(gè)狀態(tài)用來消除該操作的影響。Command具體對(duì)象調(diào)用一個(gè)Unexecute操作,該操作取消上一次Execute調(diào)用的效果。執(zhí)行的命令被存儲(chǔ)在一個(gè)歷史列表中??赏ㄟ^向后和向前遍歷這一列表并分別調(diào)用Unexecute和Execute來實(shí)現(xiàn)次數(shù)不限的“取消”和“重做”。Command模式將調(diào)用操作的對(duì)象與知道如何實(shí)現(xiàn)該操作的對(duì)象解耦。Command可像其他的對(duì)象一樣被方便的替換和擴(kuò)展。在菜單中增加新的調(diào)用命令時(shí)只要增加新的Command,而無需改變已有的類。我們將Command對(duì)象放到控制器中,控制器接收視圖的輸入并解析,將用戶輸入發(fā)送給Command對(duì)象,Command對(duì)象調(diào)用執(zhí)行接口,然后在Command子類中將用戶輸入發(fā)給模型,模型執(zhí)行邏輯維護(hù)數(shù)據(jù),并通知視圖。視圖得到通知后獲取模型的新數(shù)據(jù)并更新自己。項(xiàng)目的界面框架采用這種MVC模式使得軟件變得靈活、易于擴(kuò)展和維護(hù)。
四、結(jié)論
在軟件開發(fā)的過程中,開發(fā)人員最為擔(dān)心的是需求的不斷變化,而這些變化又不是開發(fā)人員所能控制的,因此,為了適應(yīng)這些變化,就要使用設(shè)計(jì)模式。MVC模式在一個(gè)解決方案中綜合運(yùn)用多種設(shè)計(jì)模式,是模式中的模式,按MVC模式的設(shè)計(jì),一個(gè)模型可以表現(xiàn)為多個(gè)視圖,這樣可以減少代碼的冗余。模型返回的數(shù)據(jù)不帶任何顯示格式,因此這些模型也可直接應(yīng)用于接口的使用。由于一個(gè)應(yīng)用程序被分離為三層,因此有時(shí)改變其中的一層就能滿足應(yīng)用的改變。一個(gè)應(yīng)用的業(yè)務(wù)流程或者業(yè)務(wù)規(guī)則的改變只需改動(dòng)MVC的模型層,而不會(huì)影響到視圖和控制器。不過,使用設(shè)計(jì)模式并不是一定就能得到一個(gè)好的設(shè)計(jì),過分地使用設(shè)計(jì)模式會(huì)增加程序的復(fù)雜性和晦澀性,讓程序不易理解,從而降低了程序的易維護(hù)性。因此要避免過度使用設(shè)計(jì)模式,我們應(yīng)根據(jù)面向?qū)ο蟮脑O(shè)計(jì)原則和實(shí)際情況綜合考慮我們的設(shè)計(jì),從而設(shè)計(jì)出具有良好擴(kuò)展性和易維護(hù)性的軟件。
愛華網(wǎng)



