MVC
歷史背景
早在上個(gè)世紀(jì)70年代哨坪,美國(guó)的施樂公司(Xerox)的工程師研發(fā)了Smalltalk編程語言庸疾,并且開始用它編寫圖形界面的應(yīng)用程序。而在Smalltalk-80這個(gè)版本的時(shí)候当编,一位叫Trygve Reenskaug的工程師設(shè)計(jì)了MVC圖形應(yīng)用程序的架構(gòu)模式彼硫,極大地降低了圖形應(yīng)用程序的管理難度。而在四人幫(GoF)的設(shè)計(jì)模式當(dāng)中并沒有把MVC當(dāng)做是設(shè)計(jì)模式,而僅僅是把它看成解決問題的一些類的集合拧篮。Smalltalk-80 MVC和GoF描述的MVC是最經(jīng)典的MVC模式词渤。
MVC的依賴關(guān)系
MVC出了把應(yīng)用程序分成View、Model層串绩,還額外的加了一個(gè)Controller層缺虐,它的職責(zé)為進(jìn)行Model和View之間的協(xié)作(路由、輸入預(yù)處理等)的應(yīng)用邏輯(application logic)礁凡;Model進(jìn)行處理業(yè)務(wù)邏輯高氮。Model、View顷牌、Controller三個(gè)層次的依賴關(guān)系如下:
Controller和View都依賴Model層剪芍,Controller和View可以互相依賴。在一些網(wǎng)上的資料Controller和View之間的依賴關(guān)系可能不一樣窟蓝,有些是單向依賴罪裹,有些是雙向依賴,這個(gè)其實(shí)關(guān)系不大运挫,后面會(huì)看到它們的依賴關(guān)系都是為了把處理用戶行為觸發(fā)的事件處理權(quán)交給Controller状共。
MVC的調(diào)用關(guān)系
用戶的對(duì)View操作以后,View捕獲到這個(gè)操作谁帕,會(huì)把處理的權(quán)利交移給Controller(Pass calls)峡继;Controller會(huì)對(duì)來自View數(shù)據(jù)進(jìn)行預(yù)處理、決定調(diào)用哪個(gè)Model的接口匈挖;然后由Model執(zhí)行相關(guān)的業(yè)務(wù)邏輯碾牌;當(dāng)Model變更了以后,會(huì)通過觀察者模式(Observer Pattern)通知View儡循;View通過觀察者模式收到Model變更的消息以后小染,會(huì)向Model請(qǐng)求最新的數(shù)據(jù),然后重新更新界面贮折。如下圖:
看似沒有什么特別的地方裤翩,但是由幾個(gè)需要特別關(guān)注的關(guān)鍵點(diǎn):
View是把控制權(quán)交移給Controller,Controller執(zhí)行應(yīng)用程序相關(guān)的應(yīng)用邏輯(對(duì)來自View數(shù)據(jù)進(jìn)行預(yù)處理调榄、決定調(diào)用哪個(gè)Model的接口等等)踊赠。
Controller操作Model,Model執(zhí)行業(yè)務(wù)邏輯對(duì)數(shù)據(jù)進(jìn)行處理每庆。但不會(huì)直接操作View筐带,可以說它是對(duì)View無知的。
View和Model的同步消息是通過觀察者模式進(jìn)行缤灵,而同步操作是由View自己請(qǐng)求Model的數(shù)據(jù)然后對(duì)視圖進(jìn)行更新伦籍。
需要特別注意的是MVC模式的精髓在于第三點(diǎn):Model的更新是通過觀察者模式告知View的蓝晒,具體表現(xiàn)形式可以是Pub/Sub或者是觸發(fā)Events。而網(wǎng)上很多對(duì)于MVC的描述都沒有強(qiáng)調(diào)這一點(diǎn)帖鸦。通過觀察者模式的好處就是:不同的MVC三角關(guān)系可能會(huì)有共同的Model芝薇,一個(gè)MVC三角中的Controller操作了Model以后,兩個(gè)MVC三角的View都會(huì)接受到通知作儿,然后更新自己洛二。保持了依賴同一塊Model的不同View顯示數(shù)據(jù)的實(shí)時(shí)性和準(zhǔn)確性。我們每天都在用的觀察者模式攻锰,在幾十年前就已經(jīng)被大神們整合到MVC的架構(gòu)當(dāng)中晾嘶。
這里有一個(gè)MVC模式的JavaScript Demo,實(shí)現(xiàn)了一個(gè)小的TodoList應(yīng)用程序娶吞。經(jīng)典的Smalltalk-80 MVC不需要任何框架支持就可以實(shí)現(xiàn)垒迂。目前Web前端框架當(dāng)中只有一個(gè)號(hào)稱是嚴(yán)格遵循Smalltalk-80 MVC模式的:maria.js。
MVC的優(yōu)缺點(diǎn)
優(yōu)點(diǎn):
把業(yè)務(wù)邏輯和展示邏輯分離妒蛇,模塊化程度高机断。且當(dāng)應(yīng)用邏輯需要變更的時(shí)候,不需要變更業(yè)務(wù)邏輯和展示邏輯材部,只需要Controller換成另外一個(gè)Controller就行了(Swappable Controller)毫缆。
觀察者模式可以做到多視圖同時(shí)更新唯竹。缺點(diǎn):
Controller測(cè)試?yán)щy乐导。因?yàn)橐晥D同步操作是由View自己執(zhí)行,而View只能在有UI的環(huán)境下運(yùn)行浸颓。在沒有UI環(huán)境下對(duì)Controller進(jìn)行單元測(cè)試的時(shí)候物臂,應(yīng)用邏輯正確性是無法驗(yàn)證的:Model更新的時(shí)候,無法對(duì)View的更新操作進(jìn)行斷言涂籽。
View無法組件化馅精。View是強(qiáng)依賴特定的Model的风秤,如果需要把這個(gè)View抽出來作為一個(gè)另外一個(gè)應(yīng)用程序可復(fù)用的組件就困難了。因?yàn)椴煌绦虻牡腄omain Model是不一樣的
MVC Model 2
在Web服務(wù)端開發(fā)的時(shí)候也會(huì)接觸到MVC模式仪媒,而這種MVC模式不能嚴(yán)格稱為MVC模式。經(jīng)典的MVC模式只是解決客戶端圖形界面應(yīng)用程序的問題谢鹊,而對(duì)服務(wù)端無效算吩。服務(wù)端的MVC模式又自己特定的名字:MVC Model 2,或者叫JSP Model 2佃扼,或者直接就是Model 2 偎巢。Model 2客戶端服務(wù)端的交互模式如下:
服務(wù)端接收到來自客戶端的請(qǐng)求,服務(wù)端通過路由規(guī)則把這個(gè)請(qǐng)求交由給特定的Controller進(jìn)行處理兼耀,Controller執(zhí)行相應(yīng)的應(yīng)用邏輯压昼,對(duì)Model進(jìn)行操作求冷,Model執(zhí)行業(yè)務(wù)邏輯以后;然后用數(shù)據(jù)去渲染特定的模版窍霞,返回給客戶端匠题。
因?yàn)镠TTP協(xié)議是單工協(xié)議并且是無狀態(tài)的,服務(wù)器無法直接給客戶端推送數(shù)據(jù)官撼。除非客戶端再次發(fā)起請(qǐng)求梧躺,否則服務(wù)器端的Model的變更就無法告知客戶端。所以可以看到經(jīng)典的Smalltalk-80 MVC中Model通過觀察者模式告知View更新這一環(huán)被無情地打破傲绣,不能稱為嚴(yán)格的MVC掠哥。
Model 2模式最早在1998年應(yīng)用在JSP應(yīng)用程序當(dāng)中,JSP Model 1應(yīng)用管理的混亂誘發(fā)了JSP參考了客戶端MVC模式秃诵,催生了Model 2续搀。
后來這種模式幾乎被應(yīng)用在所有語言的Web開發(fā)框架當(dāng)中。PHP的ThinkPHP菠净,Python的Dijango禁舷、Flask,NodeJS的Express毅往,Ruby的RoR牵咙,基本都采納了這種模式。平常所講的MVC基本是這種服務(wù)端的MVC攀唯。
MVP
MVP模式有兩種:
Passive View
Supervising Controller
而大多數(shù)情況下討論的都是Passive View模式洁桌。本文會(huì)對(duì)PV模式進(jìn)行較為詳細(xì)的介紹,而SC模式則簡(jiǎn)單提及侯嘀。
歷史背景
MVP模式是MVC模式的改良另凌。在上個(gè)世紀(jì)90年代,IBM旗下的子公司Taligent在用C/C++開發(fā)一個(gè)叫CommonPoint的圖形界面應(yīng)用系統(tǒng)的時(shí)候提出來的戒幔。
MVP(Passive View)的依賴關(guān)系
MVP模式把MVC模式中的Controller換成了Presenter吠谢。MVP層次之間的依賴關(guān)系如下:
MVP打破了View原來對(duì)于Model的依賴,其余的依賴關(guān)系和MVC模式一致诗茎。
MVP(Passive View)的調(diào)用關(guān)系
既然View對(duì)Model的依賴被打破了工坊,那View如何同步Model的變更?看看MVP的調(diào)用關(guān)系:
和MVC模式一樣敢订,用戶對(duì)View的操作都會(huì)從View交移給Presenter王污。Presenter會(huì)執(zhí)行相應(yīng)的應(yīng)用程序邏輯,并且對(duì)Model進(jìn)行相應(yīng)的操作枢析;而這時(shí)候Model執(zhí)行完業(yè)務(wù)邏輯以后玉掸,也是通過觀察者模式把自己變更的消息傳遞出去,但是是傳給Presenter而不是View醒叁。Presenter獲取到Model變更的消息以后司浪,通過View提供的接口更新界面泊业。
關(guān)鍵點(diǎn):
View不再負(fù)責(zé)同步的邏輯,而是由Presenter負(fù)責(zé)啊易。Presenter中既有應(yīng)用程序邏輯也有同步邏輯吁伺。
View需要提供操作界面的接口給Presenter進(jìn)行調(diào)用。(關(guān)鍵)
對(duì)比在MVC中租谈,Controller是不能操作View的篮奄,View也沒有提供相應(yīng)的接口;而在MVP當(dāng)中割去,Presenter可以操作View窟却,View需要提供一組對(duì)界面操作的接口給Presenter進(jìn)行調(diào)用;Model仍然通過事件廣播自己的變更呻逆,但由Presenter監(jiān)聽而不是View夸赫。
MVP模式,這里也提供一個(gè)用JavaScript編寫的例子咖城。
MVP(Passive View)的優(yōu)缺點(diǎn)
優(yōu)點(diǎn):
便于測(cè)試茬腿。Presenter對(duì)View是通過接口進(jìn)行,在對(duì)Presenter進(jìn)行不依賴UI環(huán)境的單元測(cè)試的時(shí)候宜雀∏衅剑可以通過Mock一個(gè)View對(duì)象,這個(gè)對(duì)象只需要實(shí)現(xiàn)了View的接口即可辐董。然后依賴注入到Presenter中悴品,單元測(cè)試的時(shí)候就可以完整的測(cè)試Presenter應(yīng)用邏輯的正確性。這里根據(jù)上面的例子給出了Presenter的單元測(cè)試樣例郎哭。
View可以進(jìn)行組件化他匪。在MVP當(dāng)中菇存,View不依賴Model夸研。這樣就可以讓View從特定的業(yè)務(wù)場(chǎng)景中脫離出來,可以說View可以做到對(duì)業(yè)務(wù)完全無知依鸥。它只需要提供一系列接口提供給上層操作亥至。這樣就可以做到高度可復(fù)用的View組件。缺點(diǎn):
Presenter中除了應(yīng)用邏輯以外贱迟,還有大量的View->Model姐扮,Model->View的手動(dòng)同步邏輯,造成Presenter比較笨重衣吠,維護(hù)起來會(huì)比較困難茶敏。
MVP(Supervising Controller)
上面講的是MVP的Passive View模式,該模式下View非常Passive缚俏,它幾乎什么都不知道惊搏,Presenter讓它干什么它就干什么贮乳。而Supervising Controller模式中,Presenter會(huì)把一部分簡(jiǎn)單的同步邏輯交給View自己去做恬惯,Presenter只負(fù)責(zé)比較復(fù)雜的向拆、高層次的UI操作,所以可以把它看成一個(gè)Supervising Controller酪耳。
Supervising Controller模式下的依賴和調(diào)用關(guān)系:
因?yàn)镾upervising Controller用得比較少浓恳,對(duì)它的討論就到這里為止。
MVVM
MVVM可以看作是一種特殊的MVP(Passive View)模式碗暗,或者說是對(duì)MVP模式的一種改良颈将。
歷史背景
MVVM模式最早是微軟公司提出,并且了大量使用在.NET的WPF和Sliverlight中言疗。2005年微軟工程師John Gossman在自己的博客上首次公布了MVVM模式吆鹤。
ViewModel
MVVM代表的是Model-View-ViewModel,這里需要解釋一下什么是ViewModel洲守。ViewModel的含義就是 "Model of View"疑务,視圖的模型。它的含義包含了領(lǐng)域模型(Domain Model)和視圖的狀態(tài)(State)梗醇。 在圖形界面應(yīng)用程序當(dāng)中知允,界面所提供的信息可能不僅僅包含應(yīng)用程序的領(lǐng)域模型。還可能包含一些領(lǐng)域模型不包含的視圖狀態(tài)叙谨,例如電子表格程序上需要顯示當(dāng)前排序的狀態(tài)是順序的還是逆序的温鸽,而這是Domain Model所不包含的,但也是需要顯示的信息手负。
可以簡(jiǎn)單把ViewModel理解為頁面上所顯示內(nèi)容的數(shù)據(jù)抽象涤垫,和Domain Model不一樣,ViewModel更適合用來描述View竟终。
MVVM的依賴
MVVM的依賴關(guān)系和MVP依賴蝠猬,只不過是把P換成了VM。
MVVM的調(diào)用關(guān)系
MVVM的調(diào)用關(guān)系和MVP一樣统捶。但是榆芦,在ViewModel當(dāng)中會(huì)有一個(gè)叫Binder,或者是Data-binding engine的東西喘鸟。以前全部由Presenter負(fù)責(zé)的View和Model之間數(shù)據(jù)同步操作交由給Binder處理匆绣。你只需要在View的模版語法當(dāng)中,指令式地聲明View上的顯示的內(nèi)容是和Model的哪一塊數(shù)據(jù)綁定的什黑。當(dāng)ViewModel對(duì)進(jìn)行Model更新的時(shí)候崎淳,Binder會(huì)自動(dòng)把數(shù)據(jù)更新到View上去,當(dāng)用戶對(duì)View進(jìn)行操作(例如表單輸入)愕把,Binder也會(huì)自動(dòng)把數(shù)據(jù)更新到Model上去拣凹。這種方式稱為:Two-way data-binding茵瘾,雙向數(shù)據(jù)綁定「篮祝可以簡(jiǎn)單而不恰當(dāng)?shù)乩斫鉃橐粋€(gè)模版引擎拗秘,但是會(huì)根據(jù)數(shù)據(jù)變更實(shí)時(shí)渲染。
也就是說祈惶,MVVM把View和Model的同步邏輯自動(dòng)化了雕旨。以前Presenter負(fù)責(zé)的View和Model同步不再手動(dòng)地進(jìn)行操作,而是交由框架所提供的Binder進(jìn)行負(fù)責(zé)捧请。只需要告訴Binder凡涩,View顯示的數(shù)據(jù)對(duì)應(yīng)的是Model哪一部分即可。
這里有一個(gè)JavaScript MVVM的例子疹蛉,因?yàn)镸VVM需要Binder引擎活箕。所以例子中使用了一個(gè)MVVM的庫:Vue.js。
MVVM的優(yōu)缺點(diǎn)
優(yōu)點(diǎn):
提高可維護(hù)性可款。解決了MVP大量的手動(dòng)View和Model同步的問題育韩,提供雙向綁定機(jī)制。提高了代碼的可維護(hù)性闺鲸。
簡(jiǎn)化測(cè)試筋讨。因?yàn)橥竭壿嬍墙挥葿inder做的,View跟著Model同時(shí)變更摸恍,所以只需要保證Model的正確性悉罕,View就正確。大大減少了對(duì)View同步更新的測(cè)試立镶。缺點(diǎn):
過于簡(jiǎn)單的圖形界面不適用壁袄,或說牛刀殺雞。
對(duì)于大型的圖形應(yīng)用程序媚媒,視圖狀態(tài)較多嗜逻,ViewModel的構(gòu)建和維護(hù)的成本都會(huì)比較高。
數(shù)據(jù)綁定的聲明是指令式地寫在View的模版當(dāng)中的欣范,這些內(nèi)容是沒辦法去打斷點(diǎn)debug的变泄。
結(jié)語
可以看到令哟,從MVC->MVP->MVVM恼琼,就像一個(gè)打怪升級(jí)的過程。后者解決了前者遺留的問題屏富,把前者的缺點(diǎn)優(yōu)化成了優(yōu)點(diǎn)晴竞。同樣的Demo功能,代碼從最開始的一堆文件狠半,優(yōu)化成了最后只需要20幾行代碼就完成噩死。MV*模式之間的區(qū)分還是蠻清晰的颤难,希望可以給對(duì)這些模式理解比較模糊的同學(xué)帶來一些參考和思路