mvc和mvvm的區別

前言

mvc和mvvm大概是個老生常談的問題了,關於MVC和MVVM如此這般設計的原因以及我們應該如何思考一些相關的問題


1.在看mvc和mvvm的區別之前我們來看一下前端的發展歷史

在上個世紀的1989年,歐洲核子研究中心的物理學家Tim Berners-Lee發明了超文本標記語言(HyperText Markup Language),簡稱HTML,並在1993年成爲互聯網草案。從此,互聯網開始迅速商業化,誕生了一大批商業網站。

最早的HTML頁面是完全靜態的網頁,它們是預先編寫好的存放在Web服務器上的html文件。瀏覽器請求某個URL時,Web服務器把對應的html文件扔給瀏覽器,就可以顯示html文件的內容了。

如果要針對不同的用戶顯示不同的頁面,顯然不可能給成千上萬的用戶準備好成千上萬的不同的html文件,所以,服務器就需要針對不同的用戶,動態生成不同的html文件。一個最直接的想法就是利用C、C++這些編程語言,直接向瀏覽器輸出拼接後的字符串。這種技術被稱爲CGI:Common Gateway Interface。

很顯然,像新浪首頁這樣的複雜的HTML是不可能通過拼字符串得到的。於是,人們又發現,其實拼字符串的時候,大多數字符串都是HTML片段,是不變的,變化的只有少數和用戶相關的數據,所以,又出現了新的創建動態HTML的方式:ASP、JSP和PHP——分別由微軟、SUN和開源社區開發。

在ASP中,一個asp文件就是一個HTML,但是,需要替換的變量用特殊的<%=var%>標記出來了,再配合循環、條件判斷,創建動態HTML就比CGI要容易得多。

但是,一旦瀏覽器顯示了一個HTML頁面,要更新頁面內容,唯一的方法就是重新向服務器獲取一份新的HTML內容。如果瀏覽器想要自己修改HTML頁面的內容,就需要等到1995年年底,JavaScript被引入到瀏覽器。

有了JavaScript後,瀏覽器就可以運行JavaScript,然後,對頁面進行一些修改。JavaScript還可以通過修改HTML的DOM結構和CSS來實現一些動畫效果,而這些功能沒法通過服務器完成,必須在瀏覽器實現。

用JavaScript在瀏覽器中操作HTML,經歷了若干發展階段:
第一階段,直接用JavaScript操作DOM節點,使用瀏覽器提供的原生API:
第二階段,由於原生API不好用,還要考慮瀏覽器兼容性,jQuery橫空出世,以簡潔的API迅速俘獲了前端開發者的芳心:
第三階段,MVC模式,需要服務器端配合,JavaScript可以在前端修改服務器渲染後的數據。
現在,隨着前端頁面越來越複雜,用戶對於交互性要求也越來越高,想要寫出Gmail這樣的頁面,僅僅用jQuery是遠遠不夠的。MVVM模型應運而生。

MVVM最早由微軟提出來,它借鑑了桌面應用程序的MVC思想,在前端頁面中,把Model用純JavaScript對象表示,View負責顯示,兩者做到了最大限度的分離。

2.現在讓我們從MVC開始

幾乎所有的App都只幹這麼一件事:將數據展示給用戶看,並處理用戶對界面的操作。
MVC的思想:一句話描述就是Controller負責將Model的數據用View顯示出來,換句話說就是在Controller裏面把Model的數據賦值給View,比如在controller中寫document.getElementById("box").innerHTML = data[”title”],只是還沒有刻意建一個Model類出來而已。



M、V、C

Model(模型):是應用程序中用於處理應用程序數據邏輯的部分。
    通常模型對象負責在數據庫中存取數據。

比如我們人類有一雙手,一雙眼睛,一個腦袋,沒有尾巴,這就是模型,Model定義了這個模塊的數據模型。
在代碼中體現爲數據管理者,Model負責對數據進行獲取及存放。
數據不可能憑空生成的,要麼是從服務器上面獲取到的數據,要麼是本地數據庫中的數據,
也有可能是用戶在UI上填寫的表單即將上傳到服務器上面存放,所以需要有數據來源。
既然Model是數據管理者,則自然由它來負責獲取數據。
Controller不需要關心Model是如何拿到數據的,只管調用就行了。
數據存放的地方是在Model,而使用數據的地方是在Controller,
所以Model應該提供接口供controller訪問其存放的數據(通常通過.h裏面的只讀屬性)

View(視圖):是應用程序中處理數據顯示的部分。
    通常視圖是依據模型數據創建的。

View,視圖,簡單來說,就是我們在界面上看見的一切。
它們有一部分是我們UI定死的,也就是不會根據數據來更新顯示的,
比如一些Logo圖片啊,這裏有個按鈕啊,那裏有個輸入框啊,一些顯示特定內容的label啊等等;
有一部分是會根據數據來顯示內容的,比如tableView來顯示好友列表啊,
這個tableView的顯示內容肯定是根據數據來顯示的。
我們使用MVC解決問題的時候,通常是解決這些根據數據來顯示內容的視圖。

Controller(控制器):是應用程序中處理用戶交互的部分。
    通常控制器負責從視圖讀取數據,控制用戶輸入,並向模型發送數據。

Controller是MVC中的數據和視圖的協調者,也就是在Controller裏面把Model的數據賦值給View來顯示
(或者是View接收用戶輸入的數據然後由Controller把這些數據傳給Model來保存到本地或者上傳到
服務器)。

綜合以上內容,實際上你應該可以通過面向對象的基本思想來推導出controller出現的原因:我們所有的App都是界面和數據的交互,所以需要類來進行界面的繪製,於是出現了View,需要類來管理數據於是出現了Model。我們設計的View應該能顯示任意的內容比如頁面中顯示的文字應該是任意的而不只是某個特定Model的內容,所以我們不應該在View的實現中去寫和Model相關的任何代碼,如果這樣做了,那麼View的可擴展性就相當低了。而Model只是負責處理數據的,它根本不知道數據到時候會拿去幹啥,可能拿去作爲算法噼裏啪啦去了,可能拿去顯示給用戶了,它既然無法接收用戶的交互,它就不應該去管和視圖相關的任何信息,所以Model中不應該寫任何View相關代碼。然而我們的數據和界面應該同步,也就是一定要有個地方要把Model的數據賦值給View,而Model內部和View的內部都不可能去寫這樣的代碼,所以只能新創造一個類出來了,取名爲Controller。

3.下面來看這張圖

斯坦福大學公開課上的這幅圖來說明,這可以說是最經典和最規範的MVC標準


mvc.png

這張圖把MVC分爲三個獨立的區域,並且中間用了一些線來隔開。很有意思的設計,因爲這些線似乎出現在了駕校科目一的內容中,你瞧C和V以及C和M之間的白線,一部分是虛線一部分是實線對吧,這就表明了引用關係:C可以直接引用V和M,而V和M不能直接引用C,至少你不能顯式的在V和M的代碼中去寫和C相關的任何代碼,而V和M之間則是雙黃線,沒錯,它們倆誰也不能引用誰,你既不能在M裏面寫V,也不能在V裏面寫M。哦,上面的描述有點小小的問題,你不是“不能”這樣寫,而是“不應該”這樣寫,沒人能阻止你在寫代碼的時候在一個M裏面去寫V,但是一旦你這樣做了,那麼你就違背了MVC的規範,你就不是在使用MVC了,所以這算是MVC的一個必要條件:使用MVC –> M裏面沒有V的代碼。所以M裏面沒有V的代碼就是使用MVC的必要條件。

View和Controller的交互

按鈕點擊事件,是View來接收的,但是處理這個事件的應該是Controller,所以View把這個事件傳遞給了Controller,如何傳遞的呢,見圖,看到View上面的action沒有,這就是事件,看到Controller上面的target沒有,這就是靶子,View究竟要把事件傳遞給誰,它被規定了傳遞給靶子,Controller實際上就是靶子。只是View只負責傳遞事件,不負責關心靶子是誰。就像你是一個負責運貨的少年,你唯一知道的是你要把貨(action)交給上頭(開發者)告訴你的那個收貨的人(target),至於那個收貨的人是警察還是怪獸,你都不需要關心。這是V和C的一種交互方式,叫做target-action。所以你看,這張圖簡直就是神來之筆,旁邊還栩栩如生的畫出了V對C的另一種傳值:協議-委託。委託有兩種:代理和數據源。什麼是代理,就是專門處理should、will、did事件的委託,什麼是數據源,就是專門處理data、count等等的委託。


Model和Controller的交互

M是幹嘛的?上面說了,M就是數據管理者,你可以理解爲它直接和數據庫打交道。這裏的數據庫可能是本地的,也可能是服務器上的,M會從數據庫獲取數據,也可能把數據上傳給數據庫。M也將提供屬性或者接口來供C訪問其持有的數據。我們就拿一個簡單的需求作爲例子,假如我想在一個模塊中顯示一段文字,這段文字是從網上獲取下來的。

那麼使用MVC的話,在C中肯定需要一個UILabel(V)作爲屬性來顯示這段文字,而這段文字由誰來獲取呢,肯定是由M來獲取了。而獲取的地方在哪裏呢?通常在C的生命週期裏面,所以往往是在C的一個生命週期方法比如viewDidLoad裏面調用M獲取數據的方法來獲取數據。現在問題來了,M獲取數據的方法是異步的網絡請求,網絡請求結束後,C才應該用請求下來的數據重新賦值給V,現在的問題是,C如何知道網絡請求結束了?

這裏我們一定要換一種角度去思考,我們進一步考慮M和V之間的關係:它們應該是一種同步的關係,也就是,不管任何時刻,只要M的值發生改變,V的顯示就應該發生改變(顯示最新的M的內容)。所以我們可以關注M的值改變,而不用關心M的網絡請求是否結束了。實際上C根本不知道M從哪去拿的數據,C的責任是負責把M最新的數據賦值給V。所以C應該關注的事件是:M的值是否發生了變化。

4.MVVM

MVVM的誕生

就像我們之前分析MVC是如何合理分配工作的一樣,我們需要數據所以有了M,我們需要界面所以有了V,而我們需要找一個地方把M賦值給V來顯示,所以有了C,然而我們忽略了一個很重要的操作:數據解析。在MVC出生的年代,手機APP的數據往往都比較簡單,沒有現在那麼複雜,所以那時的數據解析很可能一步就解決了,所以既然有這樣一個問題要處理,而面向對象的思想就是用類和對象來解決問題,顯然V和M早就被定義死了,它們都不應該處理“解析數據”的問題,理所應當的,“解析數據”這個問題就交給C來完成了。而現在的手機App功能越來越複雜,數據結構也越來越複雜,所以數據解析也就沒那麼簡單了。如果我們繼續按照MVC的設計思路,將數據解析的部分放到了Controller裏面,那麼Controller就將變得相當臃腫。還有相當重要的一點:Controller被設計出來並不是處理數據解析的。1、管理自己的生命週期;2、處理Controller之間的跳轉;3、實現Controller容器。這裏面根本沒有“數據解析”這一項,所以顯然,數據解析也不應該由Controller來完成。那麼我們的MVC中,M、V、C都不應該處理數據解析,那麼由誰來呢?這個問題實際上在面向對象的時候相當好回答:既然目前沒有類能夠處理這個問題,那麼就創建一個新的類出來解決不就好了?所以我們聰明的開發者們就專門爲數據解析創建出了一個新的類:ViewModel。這就是MVVM的誕生。


如何實現MVVM

搞清楚了MVVM爲什麼會出現,將對於你理解如何實現MVVM有極大的幫助。在我們開始着手實現MVVM之前,我先簡單提一下之前遺留的一個問題:爲什麼MVVM這個名字裏面,沒有Controller的出現(爲什麼不叫MVCVM,C去哪了)。本來這個問題應該在實現後再來解釋,但是我們這裏是教學,爲了讓大家更好的明白我們接下來的思想,所以這裏要提前解釋一下這個結論:Controller的存在感被完全的降低了。我們在待會實現MVVM的時候你就能體會到了,這裏請先把這個結論印在腦海當中:Controller的存在感被完全的降低了、Controller的存在感被完全的降低了、Controller的存在感被完全的降低了。

好的,我們終於要開始着手實現MVVM了。如果你已經搞懂了MVC,那麼用MVVM實現一個相同的功能將會變得非常簡單。你只需要記住兩點:1、Controller的存在感被完全的降低了;2、VM的出現就是Controller存在感降低的原因。


Controller存在感降低的原因

在MVVM中,Controller不再像MVC那樣直接持有Model了。想象Controller是一個Boss,數據是一堆文件(Model),如果現在是MVC,那麼數據解析(比如整理文件)需要由Boss親自完成,然而實際上Boss需要的僅僅是整理好的文件而不是那一堆亂七八糟的整理前的文件。所以Boss招聘了一個祕書,現在Boss就不再需要管理原始數據(整理之前的文件)了,他只需要去找祕書:你幫我把文件整理好後給我。那麼這個祕書就首先去拿到文件(原始數據),然後進行整理(數據解析),接下來把整理的結果給Boss。所以祕書就是VM了,並且Controller(Boss)現在只需要直接持有VM而不需要再持有M了。如果再進一步理解C、VM、M之間的關係:因爲Controller只需要數據解析的結果而不關心過程,所以就相當於VM把“如何解析Model”給封裝起來了,C甚至根本就不需要知道M的存在就能把工作做好,前提它需要持有一個VM。那麼我們MVVM中的持有關係就是:C持有VM,VM持有M。這裏有一個比較爭議的地方:C該不該持有M。我的答案是不該。爲什麼呢,因爲C持有M沒有任何意義。就算C直接拿到了M的數據,它還是要去讓VM進行數據解析,而數據解析就需要M,那麼直接讓VM持有M而C直接持有VM就足夠了。最後再分享一個我在實現MVVM中的一個技巧,也談不上是技巧吧,算是一種必要的思想:一旦在實現Controller的過程中遇到任何跟Model(或者數據)相關的問題,就找VM要答案。這個思想待會我們會在實現代碼的時候用到。

本文參考之https://blog.csdn.net/u013282174/article/details/51220199

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章