Classic MVC
Classic MVC 大概上世紀七十年代,Xerox PARC的Trygve提出了MVC的概念。
並應用在Smalltalk系統中,為了和其它類型的MVC加以區分,歷史上習慣的稱之為Classic MVC。
Classic Mvc模式
Model:封裝領域數據及邏輯
View:查詢領域數據並展現給用戶
Conctroller:截獲用戶請求並改變領域數據
從依賴關系看,Model不依賴View和Controller,而View和Controller依賴Model。
Classic MVC關注兩個分離:
從Model中分離View
從View中分離Controller
從Model中分離View,主要基於以下幾點考慮:
不同的關注點:Model關注內在的不可視的邏輯,而View關注外在的可視的邏輯。
多種表現形式:同一個Model往往需要多種View表現形式,如文本、圖像。
提高可測試性:相對Model而言,View是不容易測試的。
從View中分離Controller就不那么重要了。
開發軟件的時代,View和Controller往往是一一對應的關系,所以常常把他們合並成為UI,事實上,當時多數UI框架都沒有實現從View中分離Controller。后來隨着Web的興起,這種分離(模板技術)才開始流行起來。
本質上Classic MVC的結構如下圖所示,之所以說本質上,是因為View和Controller其實是彼此關聯的,但這種關聯和稍后提到的MVP完全不同,更像是一種框架的副產品,為了避免引起混淆,這里省略了它們,具體參閱:How to use Model-View-Controller (MVC)
圖解:
Controller截獲用戶通過鼠標或鍵盤發出的請求,然后改變Model的狀態,Model通過Observer Synchronization通知View自己的狀態發生了變化
View查詢Model展現數據。
Classic MVC並不完美,不適用於復雜的邏輯。
舉個例子:
用戶通過鼠標拖動滾動條來調整音量大小,如果音量大於某個數值,背景色變紅以示提醒。當使用Classic MVC的時候,如何處理背景色變紅的邏輯呢?
有兩個選擇:
Model觸發一個特殊事件,View收到后完成相關邏輯的處理。但我們前面說過,從依賴關系上看,Model應該完全無視View的存在。
在View中判斷音量臨界值,達到后完成相關邏輯的處理。但我們前面說過,View是不容易測試的,應該盡可能減少邏輯處理。
Application Model MVC
大概上世紀八十年代,ParcPlace從Xerox Parc划分出來,負責Smalltalk的研發工作,為了適應更復雜的邏輯,開發了Classic MVC的改進版,也就是Application Model MVC,在原有架構基礎上引入了Application Model,如下圖所示:
圖解:
Application Model在Model和View、Controller之間扮演着一個中繼者的角色。
接着看前面的例子,既然Model和View都不適合放背景色變紅的邏輯,那么我們可以嘗試把相關邏輯放在Application Model中實現,當用戶通過鼠標調整音量大小時,Model觸發一個普通事件,Application Model攔截到這個事件,判斷音量是否大於臨界值,如果是就觸發一個特殊事件,View收到后完成相關邏輯的處理。
Application Model MVC雖然看似解決了復雜邏輯的問題,但它仍然存在硬傷:
首先隨着以微軟視窗為主的圖形化操作系統的興起,操作系統本身提供了一套原生的View接口,用來截獲用戶通過鼠標或鍵盤發出的請求。
結果讓Controller顯得多余了。
其次由於在Application Model MVC中,View的渲染只能通過事件的方式實現,Application Model不能直接操作View,所以某些情況下不能方便的實現業務邏輯。接着前面說的調節音量的例子,這次我們加個新功能,不再通過鼠標拖動滾動條來調整音量大小,而是給出一個文本框,讓用戶直接通過鍵盤輸入阿拉伯數字表示音量大小,一旦用戶輸入非法內容(比如說英文字符),背景色變黃以示警告。問題是如果用戶輸入非法內容,就不應該改變Model的狀態,但不改變Model的狀態,View就沒有機會收到渲染的事件。
MVP
大概上世紀九十年代,IBM的Mike Potel提出了MVP的概念。與此同時,Smalltalk團隊正在開發新一代框架,當他們看到MVP時,發現它不僅和MVC非常相似,並且很好的解決了復雜邏輯的問題,所以決定使用它,出於復雜度的關系,他們簡化了MVP,最終看上去更像是把原本的MVC扭轉了一個角度,把其中的VC顛倒了一下順序:
圖解:
View截獲用戶請求,然后委派給Presenter,Presenter改變Model的狀態,Model通過Observer Synchronization通知View自己的狀態發生了變化,View查詢Model展現數據。
最重要的是一點是Presenter和View彼此持有對方的引用。雖然View截獲用戶請求,但它並不處理,而是委派給Presenter處理,保證了可測試性,同時,因為Presenter可以直接操作View,不必受限於觀察者模式。
接着前面說的調節音量的例子,當用戶通過鼠標拖動滾動條來調整音量大小時,View截獲請求,並把請求委派給Presenter,如果Presenter發現音量大於臨界值,直接操作View實現邏輯;當用戶通過鍵盤輸入音量大小時,View截獲請求,並把請求委派給Presenter,如果Presenter發現內容非法,直接操作View實現邏輯。
Martin Fowler分析了MVP的實現方式,分類為Supervising Controller和Passive View。
圖解:
MVP的兩種分類:Supervising Controller和Passive View
二者的區別在於Model和View是否有聯系,在Supervising Controller的實現中,View可以查詢Model,Model狀態發生變化的話會通知View,而在Passive View的實現中,View不可以查詢Model,Model狀態發生變化的話會通知Presenter,由Presenter完成View的渲染。比較而言,Passive View的可測試性更好一些,但Presenter的代碼量相應大些。
前面我們討論了MVC到MVP的演化史,隨着Web的興起,人們開始把MVC,MVP等知識應用到Web環境下,但Web環境有其特殊性,最重要的一點就是HTTP是無狀態的,每次請求都是獨立的,所以不可能實現觀察者模式。
Web MVC
br />Java是Web MVC最早的實踐者,開發出Model 2,使用JavaBean,JSP,Servlet分別對應MVC中的三個組成部分,緊接着Structs的出現開始讓大眾注意到Web MVC,不過真正讓Web MVC流行起來的卻是Ruby社區的Rails,其大致流程如下圖所示:
圖解:
一個典型的Web MVC流程
Controller截獲用戶發出的請求
Controller調用Model完成狀態的讀寫操作
Controller把數據傳遞給View
View渲染最終結果並呈獻給用戶
在Classic MVC中,Controler可以改變Model的狀態,View可以查詢Model的狀態,所以說對Model而言,Controller和View的地位是平等的,不過在Web MVC中,Controller變成了中繼者,主要工作是協調Model和View,如此看來,Web MVC中的Controller等同於MVP中的Presenter。那為什么不叫Web MVP,而稱之為Web MVC?這是因為截獲請求的是Controller而不是View。
Python社區的Django框架宣稱自己使用的是MTV,其實質仍然是Web MVC。
Web MVP
在Desktop的時代,微軟通過WinForms實現MVP,把組件化編程發揮到了極致,大大提升了開發效率,隨着Web的興起,微軟希望延續這樣的編程模式,所以使用WebForms實現了Web MVP,引入了CodeBehind,ViewState等設計概念。WebForms的優點和缺點都很突出,篇幅所限,具體的描述大家可以參考下面鏈接:
- 為WebForms說幾句話,以及一些ASP.NET開發上的經驗(1)
- 為WebForms說幾句話,以及一些ASP.NET開發上的經驗(2)
- 為WebForms說幾句話,以及一些ASP.NET開發上的經驗(3)
注:微軟推出了ASP.NET MVC向Web MVC靠攏,似乎要兩手抓兩手都要硬。
圖解:
微軟Web MVP vs Web MVC。注意截獲請求的是Controller還是View!