MVC,MVP 和 MVVM 模式如何選擇?

轉摘:http://www.linuxidc.com/Linux/2015-10/124622.htm

前言

做客戶端開發、前端開發對MVC、MVP、MVVM這些名詞不了解也應該大致聽過,都是為了解決圖形界面應用程序復雜性管理問題而產生的應用架構模式。

網上很多文章關于這方面的討論比較雜亂,各種MV*模式之間的區別分不清,甚至有些描述都是錯誤的。本文追根溯源,從最經典的Smalltalk-80 MVC模式開始逐步還原圖形界面之下最真實的MV*模式。


GUI程序所面臨的問題

圖形界面的應用程序提供給用戶可視化的操作界面,這個界面提供給數據和信息。用戶輸入行為(鍵盤,鼠標等)會執行一些業務邏輯,可能會導致對應用程序數據的變更,數據的變更自然需要用戶界面的同步變更以提供最準確的信息。例如用戶對一個電子表格重新排序的操作,應用程序需要響應用戶操作,對數據進行排序,然后需要同步到界面上。

在開發應用程序的時候,以求更好的管理應用程序的復雜性,基于職責分離(Speration of Duties)的思想都會對應用程序進行分層。在開發圖形界面應用程序的時候,會把管理用戶界面的層次稱為View,應用程序的數據為Model(注意這里的Model指的是Domain Model,這個應用程序對需要解決的問題的數據抽象,不包含應用的狀態,可以簡單理解為對象)。Model層對應用程序的業務邏輯無知,只保存數據結構和提供數據操作的接口

有了View和Model的分層,那么就有了兩個問題:

1、響應用戶操作的業務邏輯(例如排序)的管理。

2、View如何同步Model的變更。

帶著這兩個問題開始探索MV*模式,會發現這些模式之間的差異可以歸納為對這兩個問題處理的方式的不同。而幾乎所有的MV*模式都是經典的Smalltalk-80 MVC的修改版。

Smalltalk-80 MVC

歷史背景

早在上個世紀70年代,美國的施樂公司(Xerox)的工程師研發了Smalltalk編程語言,并且開始用它編寫圖形界面的應用程序。而在Smalltalk-80這個版本的時候,一位叫Trygve Reenskaug的工程師設計了MVC圖形應用程序的架構模式,極大地降低了圖形應用程序的管理難度。而在四人?眾(GoF)的設計模式當中并沒有把MVC當做是設計模式,而僅僅是把它看成解決問題的一些類的集合。Smalltalk-80 MVC和GoF描述的MVC是最經典的MVC模式。


MVC的依賴關系

MVC除了把應用程序分成View、Model層,還額外的加了一個Controller層,它的職責就是專門管理應用程序的業務邏輯。Model、View、Controller三個層次的依賴關系如下:

依賴關系

Controller和View都依賴Model層,Controller和View可以互相依賴。在一些網上的資料Controller和View之間的依賴關系可能不一樣,有些是單向依賴,有些是雙向依賴,這個其實關系不大,后面會看到它們的依賴關系都是為了把處理用戶行為觸發的業務邏輯的處理權交給Controller。

MVC的調用關系

用戶的對View操作以后,View捕獲到這個操作,會把處理的權利交移給Controller(Pass calls);Controller接著會執行相關的業務邏輯,這些業務邏輯可能需要對Model進行相應的操作;當Model變更了以后,會通過觀察者模式(Observer Pattern)通知View;View通過觀察者模式收到Model變更的消息以后,會向Model請求最新的數據,然后重新更新界面。如下圖:

看似沒有什么特別的地方,但是由幾個需要特別關注的關鍵點:

1、View是把控制權交移給Controller,自己不執行業務邏輯。

2、Controller執行業務邏輯并且操作Model,但不會直接操作View,可以說它是對View無知的。

3、View和Model的同步消息是通過觀察者模式進行,而同步操作是由View自己請求Model的數據然后對視圖進行更新。

需要特別注意的是MVC模式的精髓在于第三點:Model的更新是通過觀察者模式告知View的,具體表現形式可以是Pub/Sub或者是觸發Events。而網上很多對于MVC的描述都沒有強調這一點。通過觀察者模式的好處就是:不同的MVC三角關系可能會有共同的Model,一個MVC三角中的Controller操作了Model以后,兩個MVC三角的View都會接受到通知,然后更新自己。保持了依賴同一塊Model的不同View顯示數據的實時性和準確性。我們每天都在用的觀察者模式,在幾十年前就已經被大神們整合到MVC的架構當中。

這里有一個MVC模式的JavaScript Demo,實現了一個小的TodoList應用程序。經典的Smalltalk-80 MVC不需要任何框架支持就可以實現。目前Web前端框架當中只有一個號稱是嚴格遵循Smalltalk-80 MVC模式的:maria.js

MVC的優缺點

優點:

1、把業務邏輯全部分離到Controller中,模塊化程度高。當業務邏輯變更的時候,不需要變更View和Model,只需要Controller換成另外一個Controller就行了(Swappable Controller)。

2、觀察者模式可以做到多視圖同時更新。

缺點:

1、Controller測試困難。因為視圖同步操作是由View自己執行,而View只能在有UI的環境下運行。在沒有UI環境下對Controller進行單元測試的時候,Controller業務邏輯的正確性是無法驗證的:Controller更新Model的時候,無法對View的更新操作進行斷言。

2、View無法組件化。View是強依賴特定的Model的,如果需要把這個View抽出來作為一個另外一個應用程序可復用的組件就困難了。因為不同程序的的Domain Model是不一樣的

MVC Model 2

在Web服務端開發的時候也會接觸到MVC模式,而這種MVC模式不能嚴格稱為MVC模式。經典的MVC模式只是解決客戶端圖形界面應用程序的問題,而對服務端無效。服務端的MVC模式又自己特定的名字:MVC Model 2,或者叫JSP Model 2,或者直接就是Model 2 。Model 2客戶端服務端的交互模式如下:

服務端接收到來自客戶端的請求,服務端通過路由規則把這個請求交由給特定的Controller進行處理,Controller執行相應的業務邏輯,對數據庫數據(Model)進行操作,然后用數據去渲染特定的模版,返回給客戶端。

因為HTTP協議是單工協議并且是無狀態的,服務器無法直接給客戶端推送數據。除非客戶端再次發起請求,否則服務器端的Model的變更就無法告知客戶端。所以可以看到經典的Smalltalk-80 MVC中Model通過觀察者模式告知View更新這一環被無情地打破,不能稱為嚴格的MVC。

Model 2模式最早在1998年應用在JSP應用程序當中,JSP Model 1應用管理的混亂誘發了JSP參考了客戶端MVC模式,催生了Model 2。

后來這種模式幾乎被應用在所有語言的Web開發框架當中。PHP的ThinkPHP,Python的Dijango、Flask,NodeJS的Express,Ruby的RoR,基本都采納了這種模式。平常所講的MVC基本是這種服務端的MVC。

MVP

MVP模式有兩種:

1、Passive View

2、Supervising Controller

而大多數情況下討論的都是Passive View模式。本文會對PV模式進行較為詳細的介紹,而SC模式則簡單提及。

歷史背景

MVP模式是MVC模式的改良。在上個世紀90年代,IBM旗下的子公司Taligent在用C/C++開發一個叫CommonPoint的圖形界面應用系統的時候提出來的。

MVP(Passive View)的依賴關系

MVP模式把MVC模式中的Controller換成了Presenter。MVP層次之間的依賴關系如下:

MVP打破了View原來對于Model的依賴,其余的依賴關系和MVC模式一致。

MVP(Passive View)的調用關系

既然View對Model的依賴被打破了,那View如何同步Model的變更?看看MVP的調用關系:

和MVC模式一樣,用戶對View的操作都會從View交移給Presenter。Presenter同樣的會執行相應的業務邏輯,并且對Model進行相應的操作;而這時候Model也是通過觀察者模式把自己變更的消息傳遞出去,但是是傳給Presenter而不是View。Presenter獲取到Model變更的消息以后,通過View提供的接口更新界面

關鍵點:

1、View不再負責同步的邏輯,而是由Presenter負責。Presenter中既有業務邏輯也有同步邏輯。

2、View需要提供操作界面的接口給Presenter進行調用。(關鍵)

對比在MVC中,Controller是不能操作View的,View也沒有提供相應的接口;而在MVP當中,Presenter可以操作View,View需要提供一組對界面操作的接口給Presenter進行調用;Model仍然通過事件廣播自己的變更,但由Presenter監聽而不是View。

MVP模式,這里也提供一個用JavaScript編寫的例子

MVP(Passive View)的優缺點

優點:

1、便于測試。Presenter對View是通過接口進行,在對Presenter進行不依賴UI環境的單元測試的時候。可以通過Mock一個View對象,這個對象只需要實現了View的接口即可。然后依賴注入到Presenter中,單元測試的時候就可以完整的測試Presenter業務邏輯的正確性。這里根據上面的例子給出了Presenter的單元測試樣例

2、View可以進行組件化。在MVP當中,View不依賴Model。這樣就可以讓View從特定的業務場景中脫離出來,可以說View可以做到對業務邏輯完全無知。它只需要提供一系列接口提供給上層操作。這樣就可以做高度可復用的View組件。

缺點:

1、Presenter中除了業務邏輯以外,還有大量的View->Model,Model->View的手動同步邏輯,造成Presenter比較笨重,維護起來會比較困難。

MVP(Supervising Controller)

上面講的是MVP的Passive View模式,該模式下View非常Passive,它幾乎什么都不知道,Presenter讓它干什么它就干什么。而Supervising Controller模式中,Presenter會把一部分簡單的同步邏輯交給View自己去做,Presenter只負責比較復雜的、高層次的UI操作,所以可以把它看成一個Supervising Controller。

Supervising Controller模式下的依賴和調用關系:

因為Supervising Controller用得比較少,對它的討論就到這里為止。

MVVM

MVVM可以看作是一種特殊的MVP(Passive View)模式,或者說是對MVP模式的一種改良。

歷史背景

MVVM模式最早是微軟公司提出,并且了大量使用在.NET的WPF和Sliverlight中。2005年微軟工程師John Gossman在自己的博客上首次公布了MVVM模式。

ViewModel

MVVM代表的是Model-View-ViewModel,這里需要解釋一下什么是ViewModel。ViewModel的含義就是 "Model of View",視圖的模型。它的含義包含了領域模型(Domain Model)和視圖的狀態(State)。 在圖形界面應用程序當中,界面所提供的信息可能不僅僅包含應用程序的領域模型。還可能包含一些領域模型不包含的視圖狀態,例如電子表格程序上需要顯示當前排序的狀態是順序的還是逆序的,而這是Domain Model所不包含的,但也是需要顯示的信息。

可以簡單把ViewModel理解為頁面上所顯示內容的數據抽象,和Domain Model不一樣,ViewModel更適合用來描述View。

MVVM的依賴

MVVM的依賴關系和MVP依賴,只不過是把P換成了VM。

MVVM的調用關系

MVVM的調用關系和MVP一樣。但是,在ViewModel當中會有一個叫Binder,或者是Data-binding engine的東西。以前全部由Presenter負責的View和Model之間數據同步操作交由給Binder處理。你只需要在View的模版語法當中,指令式地聲明View上的顯示的內容是和Model的哪一塊數據綁定的。當ViewModel對進行Model更新的時候,Binder會自動把數據更新到View上去,當用戶對View進行操作(例如表單輸入),Binder也會自動把數據更新到Model上去。這種方式稱為:Two-way data-binding,雙向數據綁定。可以簡單而不恰當地理解為一個模版引擎,但是會根據數據變更實時渲染。

也就是說,MVVM把View和Model的同步邏輯自動化了。以前Presenter負責的View和Model同步不再手動地進行操作,而是交由框架所提供的Binder進行負責。只需要告訴Binder,View顯示的數據對應的是Model哪一部分即可。

這里有一個JavaScript MVVM的例子,因為MVVM需要Binder引擎。所以例子中使用了一個MVVM的庫:Vue.js

MVVM的優缺點

優點:

1、提高可維護性。解決了MVP大量的手動View和Model同步的問題,提供雙向綁定機制。提高了代碼的可維護性。

2、簡化測試。因為同步邏輯是交由Binder做的,View跟著Model同時變更,所以只需要保證Model的正確性,View就正確。大大減少了對View同步更新的測試。

缺點:

1、過于簡單的圖形界面不適用,或說牛刀殺雞。

2、對于大型的圖形應用程序,視圖狀態較多,ViewModel的構建和維護的成本都會比較高。

3、數據綁定的聲明是指令式地寫在View的模版當中的,這些內容是沒辦法去打斷點debug的。

結語

可以看到,從MVC->MVP->MVVM,就像一個打怪升級的過程。后者解決了前者遺留的問題,把前者的缺點優化成了優點。同樣的Demo功能,代碼從最開始的一堆文件,優化成了最后只需要20幾行代碼就完成。MV*模式之間的區分還是蠻清晰的,希望可以給對這些模式理解比較模糊的同學帶來一些參考和思路。


?參考

Scaling Isomorphic Javascript Code

Smalltalk-80 MVC

Learning JavaScript Design Patterns

Smalltalk-80 MVC in JavaScript

GUI Architectures

The Model-View-Presenter (MVP) Pattern


最后:

感謝:github.com/livoras


? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?適合自己的,才是最好的?

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,836評論 6 540
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,275評論 3 428
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,904評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,633評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,368評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,736評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,740評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,919評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,481評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,235評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,427評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,968評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,656評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,055評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,348評論 1 294
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,160評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,380評論 2 379

推薦閱讀更多精彩內容