MVC設計模式以及其他
【備註】本文來源轉載,文章來源
一、Classic MVC
MVC模式(Model-View-Controller)是軟體工程中的一種軟體架構模式,把軟體系統分為三個基本部分:模型(Model)、視圖(View)和控制器(Controller)。最早由Trygve Reenskaug在1978年提出,並應用在Smalltalk系統中。
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是不容易測試的。
Desktop軟體的時代,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是不容易測試的,應該儘可能減少邏輯處理。
Classic MVC並不完美,不適用於複雜的邏輯。舉個例子:用戶通過滑鼠拖動滾動條來調整音量大小,如果音量大於某個數值,背景色變紅以示提醒。當使用Classic MVC的時候,如何處理背景色變紅的邏輯呢?有兩個選擇:
- Model觸發一個特殊事件,View收到後完成相關邏輯的處理。但我們前面說過,從依賴關係上看,Model應該完全無視View的存在。
- 在View中判斷音量臨界值,達到後完成相關邏輯的處理。但我們前面說過,View是不容易測試的,應該儘可能減少邏輯處理。
上述只是提出了Classic MVC的一些缺陷,那到底如何解決呢?請繼續關注下文~
二、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(Model-View-Presenter)的概念。
- Model 定義使用者介面所需要被顯示的資料模型,一個模型包含著相關的商業邏輯。
- View 視圖為呈現使用者介面的終端,用以表現來自 Model 的資料,和使用者命令路由再經過 Presenter 對事件處理後的資料。
- Presenter 包含著元件的事件處理,負責檢索 Model 取得資料,和將取得的資料經過格式轉換與 View 進行溝通。
與此同時,Smalltalk團隊正在開發新一代框架,當他們看到MVP時,發現它不僅和MVC非常相似,並且很好的解決了複雜邏輯的問題,所以決定使用它,出於複雜度的關係,他們簡化了MVP,最終看上去更像是把原本的MVC扭轉了60°,把其中的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的實現方式,分類為
1、Supervising Controller
2、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
Java是Web MVC最早的實踐者,開發出Model 2,使用JavaBean,JSP,Servlet分別對應MVC中的三個組成部分,緊接著Structs的出現開始讓大眾注意到Web MVC,不過真正讓Web MVC流行起來的卻是Ruby on 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。
五、MTV
Python的Django框架宣稱自己使用的是MTV,其實質仍然是Web MVC。Django將MVC中的視圖進一步分解為 Django視圖 和 Django模板兩個部分,分別決定 「展現哪些數據」 和 「如何展現」,使得Django的模板可以根據需要隨時替換,而不僅僅限制於內置的模板。至於MVC控制器部分,由Django框架的URLconf來實現。
Django 里關注的是模型(Model)、模板(Template)和視圖(Views),分別為:
- Model,即數據存取層。 該層處理與數據相關的所有事務: 如何存取、如何驗證有效性、包含哪些行為以及數據之間的關係等。
- Template,即表現層。 該層處理與表現相關的決定: 如何在頁面或其他類型文檔中進行顯示。
- View,即業務邏輯層。 該層包含存取模型及調取恰當模板的相關邏輯。 你可以把它看作模型與模板之間的橋樑。
需要注意的是,不能簡單的把 Django視圖認為是MVC控制器,把 Django 模板認為MVC視圖。 區別在於:
- Django 視圖 不處理用戶輸入,而僅僅決定要展現哪些數據給用戶;
- Django 模板 僅僅決定如何展現Django視圖指定的數據。
六、Web MVP
在Desktop的時代,微軟通過WinForms實現MVP,把組件化編程發揮到了極致,大大提升了開發效率,隨著Web的興起,微軟希望延續這樣的編程模式,所以使用WebForms實現了Web MVP,引入了CodeBehind,ViewState等設計概念。
當一個瀏覽器向伺服器請求一個aspx頁面時的簡體步驟如下:
- 伺服器會首先創建前台頁面aspx類的對象,當子類(aspx類)被創建時,父類(後台頁面CS類)也會順便被創建。
- 接著就會在前台頁面類中調用ProcessRequest方法(PR方法不是在前台頁面類中定義的,而是在Page類中定義的,
- 因為CS類繼承與page類,而aspx類又繼承與page類,所以PR方法相當於aspx類的爺爺類中定義的)。
- 在PR方法中調用BuildControlTree方法,把前台頁面所有的html控制項和runat=server的控制項轉成對應的控制項對象並添加在前台頁面
- 類得Controls集合中(這裡當前頁面即aspx頁面類是根節點),而且runat=server的控制項對象會保存在後台CS類中的一個對應類型的變數中。
- 在PR方法中調用後台頁面CS類的Page_Load方法,這個方法中的代碼是程序員自己寫的。
- 最後再PR方法中調用Render_Controls方法,來遍歷控制項樹中每一個節點的Render_Controls方法,生成完整的html代碼
- 把完整的html代碼返回給瀏覽器。
七、http://ASP.NET MVC
微軟同時又推出了類似Web MVC的http://ASP.NET MVC,但是在截獲請求部分還是存在著一些差別,具體請看下圖:
http://ASP.NET MVC的具體工作流程為:
當用戶從瀏覽器輸入地址,發出頁面請求,到返回結果,一般經過以下步驟:
- 當用戶輸入地址,發出請求時,實際上就是向控制器發出相關命令
- 控制器接收用戶指令後,向模型請求獲得相關數據
- 模型將對應的數據返回給控制器
- 控制器將有關數據發送到指定視圖
- 指定的視圖呈現指定的數據
Web Forms構建web相對容易,開發人員只需在一個可視化設計器中拖放控制項,設置相關屬性即可,通過編寫代碼來響應事件,使得對於程序的邏輯操作非常直觀。但是,開發人員很難了解背後HTML是如何運行的,同時,如果沒有合理控制ViewState的話,頁面的尺寸將大大超過預期,使得頁面打開相當緩慢,隨著web應用的複雜化,不容易測試也是開發中面對的一個問題.
MVC避免了Web Forms所帶來的複雜性,沒有數據回傳,沒有頁面中保存視圖狀態,開發者可以完全掌握頁面呈現的全過程,使用模型、視圖及控制器將web劃分為不同的組件,有利於開發與設計的分離,也提高了程序的可維護性和擴展性,特別是利於應用程序的測試,可以比較容易的實施測試驅動開發。
兩種開發技術並存。 MVC只是給開發者提供了開發web應用程序的一種選擇,而不是替代傳統的Web Forms,這兩種技術應用於不同的場合具有不同的優缺點。具體兩者之間的比較分析,可以查看WebForms vs. MVC。
八、MVVM
Model-View-ViewModel是一種架構模式,主要在WPF、Silverlight和WP7開發里使用,它的目標是從視圖層移除幾乎所有代碼隱藏(code-behind)。MVVM是更加通用的Presentation模式的一個具體實現。MVVM視圖模型包含概念模型而不是數據模型,所有業務邏輯和其它操作都是在模型和視圖模型里完成的。
在WPF/Silverlight中應用MVVM模式,View主要用於界面呈現,ViewModel用於邏輯實現,Model用於數據的構造,而這三者能夠進行通信,最重要的是通過WPF/Silverlight中強大的數據綁定機制,將View和ViewModel有效的聯繫起來。
使用MVVM架構最大的好處是:開發人員在寫程序的時候不需要做UI,而設計人員可以使用Microsoft Expression Blend 4+設計全部的UI並且不需要寫任何代碼。主要的好處如下:
- 設計人員可以用設計工具很容易的設計UI,而且不需要寫任何代碼
- 你可以更好的設計UI,而且可以讓即使不是開發人員使用。
- 可以先設計UI或者與開發同時設計。
- 當UI全部改變時,代碼可以不改變。
為了達到以上要求。當你設計UI時,後台不能有任何代碼。並且UI與應用程序通過Bindings和Commands相互交互,其中Bindings和Commands在ViewModel中設計。
Model層主要為應用程序提供數據。其主要包含
- Web Services:SilverLight應用程序的特點就是必須通過Web service取得數據,你可以調用Web Service中的方法。
- Rest Services:和Web Services一樣
- Generic Collections:任何類型的數據集合
View Model一般有以下三個部分組成
- 屬性:一個事物,它的類型可以是一個字元型,也可以是一個對象。實現介面INotifyPropertyChanged,那麼任何UI元素綁定到這個屬性,不管這個屬性什麼時候改變都能自動和UI層交互。
- 集合:事物的集合,它的類型一般是ObservableCollection,因此,任何UI元素綁定到它,不管這個集合什麼時候改變,都可以自動的與UI交互。
- Commands:一個可以被觸發的事件,並且可以傳遞一個類型為Object的參數。但是前提是要實現介面ICommand。
這一層可以用Expression Blend設計,不用寫任何代碼。主要有以下三個部分組成
- 把View Model層的屬性綁定到 text box, radio button, toggle button, MediaElement, trigger an animation or ViewState change
- 把View Model層的集合綁定到ListBox,TreeView,DataGrid
- Commands
使用InvokeCommandAction實現以下behavior
- A、綁定View Model層的ICommand
- B、指出你需要實現的ICommand(比如Click事件,Selected事件。。。)
- C、傳遞參數
推薦閱讀:
※React+Redux的MVC方式
※Web開發中的設計模式及在DevOps中的演進 (2)
※asp.net mvc 4 模型層為什麼可以通過簡單的 {get;set}就可以對數據進行設置和取得 這其中的實現原理是什麼?
※django系列五:資料庫配置