作客戶端開發、前端開發對MVC、MVP、MVVM這些名詞不瞭解也應該大體聽過,都是爲了解決圖形界面應用程序複雜性管理問題而產生的應用架構模式。javascript
網上不少文章關於這方面的討論比較雜亂,各類MV模式之間的區別分不清,甚至有些描述都是錯誤的。本文追根溯源,從最經典的Smalltalk-80 MVC模式開始逐步還原圖形界面之下最真實的MV模式。html
GUI程序所面臨的問題前端
圖形界面的應用程序提供給用戶可視化的操做界面,這個界面提供給數據和信息。用戶輸入行爲(鍵盤,鼠標等)會執行一些業務邏輯,可能會致使對應用程序數據的變動,數據的變動天然須要用戶界面的同步變動以提供最準確的信息。例如用戶對一個電子表格從新排序的操做,應用程序須要響應用戶操做,對數據進行排序,而後須要同步到界面上。vue
在開發應用程序的時候,以求更好的管理應用程序的複雜性,基於職責分離(Speration of Duties)的思想都會對應用程序進行分層。在開發圖形界面應用程序的時候,會把管理用戶界面的層次稱爲View,應用程序的數據爲Model(注意這裏的Model指的是Domain Model,這個應用程序對須要解決的問題的數據抽象,不包含應用的狀態,能夠簡單理解爲對象)。Model層對應用程序的業務邏輯無知,只保存數據結構和提供數據操做的接口java
有了View和Model的分層,那麼就有了兩個問題:node
一、響應用戶操做的業務邏輯(例如排序)的管理。git
二、View如何同步Model的變動。github
帶着這兩個問題開始探索MV模式,會發現這些模式之間的差別能夠概括爲對這兩個問題處理的方式的不一樣。而幾乎全部的MV模式都是經典的Smalltalk-80 MVC的修改版。數據庫
歷史背景編程
早在上個世紀70年代,美國的施樂公司(Xerox)的工程師研發了Smalltalk編程語言,而且開始用它編寫圖形界面的應用程序。而在Smalltalk-80這個版本的時候,一位叫Trygve Reenskaug的工程師設計了MVC圖形應用程序的架構模式,極大地下降了圖形應用程序的管理難度。而在四人�衆(GoF)的設計模式當中並無把MVC當作是設計模式,而僅僅是把它當作解決問題的一些類的集合。Smalltalk-80 MVC和GoF描述的MVC是最經典的MVC模式。
MVC除了把應用程序分紅View、Model層,還額外的加了一個Controller層,它的職責就是專門管理應用程序的業務邏輯。Model、View、Controller三個層次的依賴關係以下:
Controller和View都依賴Model層,Controller和View能夠互相依賴。在一些網上的資料Controller和View之間的依賴關係可能不同,有些是單向依賴,有些是雙向依賴,這個其實關係不大,後面會看到它們的依賴關係都是爲了把處理用戶行爲觸發的業務邏輯的處理權交給Controller。
用戶的對View操做之後,View捕獲到這個操做,會把處理的權利交移給Controller(Pass calls);Controller接着會執行相關的業務邏輯,這些業務邏輯可能須要對Model進行相應的操做;當Model變動了之後,會經過觀察者模式(Observer Pattern)通知View;View經過觀察者模式收到Model變動的消息之後,會向Model請求最新的數據,而後從新更新界面。以下圖:
看似沒有什麼特別的地方,可是由幾個須要特別關注的關鍵點:
一、View是把控制權交移給Controller,本身不執行業務邏輯。
二、Controller執行業務邏輯而且操做Model,但不會直接操做View,能夠說它是對View無知的。
三、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。
優勢:
一、把業務邏輯所有分離到Controller中,模塊化程度高。當業務邏輯變動的時候,不須要變動View和Model,只須要Controller換成另一個Controller就好了(Swappable Controller)。
二、觀察者模式能夠作到多視圖同時更新。
缺點:
一、Controller測試困難。由於視圖同步操做是由View本身執行,而View只能在有UI的環境下運行。在沒有UI環境下對Controller進行單元測試的時候,Controller業務邏輯的正確性是沒法驗證的:Controller更新Model的時候,沒法對View的更新操做進行斷言。
二、View沒法組件化。View是強依賴特定的Model的,若是須要把這個View抽出來做爲一個另一個應用程序可複用的組件就困難了。由於不一樣程序的的Domain Model是不同的
在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模式有兩種:
一、Passive View
二、Supervising Controller
而大多數狀況下討論的都是Passive View模式。本文會對PV模式進行較爲詳細的介紹,而SC模式則簡單說起。
歷史背景
MVP模式是MVC模式的改良。在上個世紀90年代,IBM旗下的子公司Taligent在用C/C++開發一個叫CommonPoint的圖形界面應用系統的時候提出來的。
MVP模式把MVC模式中的Controller換成了Presenter。MVP層次之間的依賴關係以下:
MVP打破了View原來對於Model的依賴,其他的依賴關係和MVC模式一致。
既然View對Model的依賴被打破了,那View如何同步Model的變動?看看MVP的調用關係:
和MVC模式同樣,用戶對View的操做都會從View交移給Presenter。Presenter一樣的會執行相應的業務邏輯,而且對Model進行相應的操做;而這時候Model也是經過觀察者模式把本身變動的消息傳遞出去,可是是傳給Presenter而不是View。Presenter獲取到Model變動的消息之後,經過View提供的接口更新界面。
關鍵點:
一、View再也不負責同步的邏輯,而是由Presenter負責。Presenter中既有業務邏輯也有同步邏輯。
二、View須要提供操做界面的接口給Presenter進行調用。(關鍵)
對比在MVC中,Controller是不能操做View的,View也沒有提供相應的接口;而在MVP當中,Presenter能夠操做View,View須要提供一組對界面操做的接口給Presenter進行調用;Model仍然經過事件廣播本身的變動,但由Presenter監聽而不是View。
MVP模式,這裏也提供一個用JavaScript編寫的例子。
優勢:
一、便於測試。Presenter對View是經過接口進行,在對Presenter進行不依賴UI環境的單元測試的時候。能夠經過Mock一個View對象,這個對象只須要實現了View的接口便可。而後依賴注入到Presenter中,單元測試的時候就能夠完整的測試Presenter業務邏輯的正確性。這裏根據上面的例子給出了Presenter的單元測試樣例。
二、View能夠進行組件化。在MVP當中,View不依賴Model。這樣就可讓View從特定的業務場景中脫離出來,能夠說View能夠作到對業務邏輯徹底無知。它只須要提供一系列接口提供給上層操做。這樣就能夠作高度可複用的View組件。
缺點:
一、Presenter中除了業務邏輯之外,還有大量的View->Model,Model->View的手動同步邏輯,形成Presenter比較笨重,維護起來會比較困難。
上面講的是MVP的Passive View模式,該模式下View很是Passive,它幾乎什麼都不知道,Presenter讓它幹什麼它就幹什麼。而Supervising Controller模式中,Presenter會把一部分簡單的同步邏輯交給View本身去作,Presenter只負責比較複雜的、高層次的UI操做,因此能夠把它當作一個Supervising Controller。
Supervising Controller模式下的依賴和調用關係:
由於Supervising Controller用得比較少,對它的討論就到這裏爲止。
MVVM能夠看做是一種特殊的MVP(Passive View)模式,或者說是對MVP模式的一種改良。
歷史背景
MVVM模式最先是微軟公司提出,而且了大量使用在.NET的WPF和Sliverlight中。2005年微軟工程師John Gossman在本身的博客上首次公佈了MVVM模式。
MVVM表明的是Model-View-ViewModel,這裏須要解釋一下什麼是ViewModel。ViewModel的含義就是 "Model of View",視圖的模型。它的含義包含了領域模型(Domain Model)和視圖的狀態(State)。 在圖形界面應用程序當中,界面所提供的信息可能不只僅包含應用程序的領域模型。還可能包含一些領域模型不包含的視圖狀態,例如電子表格程序上須要顯示當前排序的狀態是順序的仍是逆序的,而這是Domain Model所不包含的,但也是須要顯示的信息。
能夠簡單把ViewModel理解爲頁面上所顯示內容的數據抽象,和Domain Model不同,ViewModel更適合用來描述View。
MVVM的依賴關係和MVP依賴,只不過是把P換成了VM。
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。
優勢:
一、提升可維護性。解決了MVP大量的手動View和Model同步的問題,提供雙向綁定機制。提升了代碼的可維護性。
二、簡化測試。由於同步邏輯是交由Binder作的,View跟着Model同時變動,因此只須要保證Model的正確性,View就正確。大大減小了對View同步更新的測試。
缺點:
一、過於簡單的圖形界面不適用,或說牛刀殺雞。
二、對於大型的圖形應用程序,視圖狀態較多,ViewModel的構建和維護的成本都會比較高。
三、數據綁定的聲明是指令式地寫在View的模版當中的,這些內容是沒辦法去打斷點debug的。
能夠看到,從MVC->MVP->MVVM,就像一個打怪升級的過程。後者解決了前者遺留的問題,把前者的缺點優化成了優勢。一樣的Demo功能,代碼從最開始的一堆文件,優化成了最後只須要20幾行代碼就完成。MV*模式之間的區分仍是蠻清晰的,但願能夠給對這些模式理解比較模糊的同窗帶來一些參考和思路。
Scaling Isomorphic Javascript Code
Learning JavaScript Design Patterns
Smalltalk-80 MVC in JavaScript
The Model-View-Presenter (MVP) Pattern
最後:
感謝:github.com/livoras
MVP+Dagger2+Retrofit2.0+Rxjava看這一個例子就夠了
若是您以爲頗有幫助,歡迎隨時撩我。