被誤解的MVC和被神化的MVVM(轉)

              轉載自:http://www.infoq.com/cn/articles/rethinking-mvc-mvvmgit

                          原文做者:唐巧github

被誤解的 MVC

MVC 的歷史

MVC,全稱是 Model View Controller,是模型 (model)-視圖 (view)-控制器 (controller) 的縮寫。它表示的是一種常見的客戶端軟件開發框架。web

MVC 的概念最先出如今二十世紀八十年代的 施樂帕克 實驗室中(對,就是那個發明圖形用戶界面和鼠標的實驗室),當時施樂帕克爲 Smalltalk 發明了這種軟件設計模式。sql

如今,MVC 已經成爲主流的客戶端編程框架,在 iOS 開發中,系統爲咱們實現好了公共的視圖類:UIView,和控制器類:UIViewController。大多數時候,咱們都須要繼承這些類來實現咱們的程序邏輯,所以,咱們幾乎逃避不開 MVC 這種設計模式。編程

可是,幾十年過去了,咱們對於 MVC 這種設計模式真的用得好嗎?其實不是的,MVC 這種分層方式雖然清楚,可是若是使用不當,極可能讓大量代碼都集中在 Controller 之中,讓 MVC 模式變成了 Massive View Controller 模式。設計模式

Controller 的臃腫問題何解?

不少人試圖解決 MVC 這種架構下 Controller 比較臃腫的問題。我還記得半年前 InfoQ 搞了一次移動座談會,當時 BeeFramework 和 Samurai-Native 的做者 老郭 問了我一句話:「什麼樣的內容才應該放到 Controller 中?」。可是當時由於時間不夠,我沒能展開個人觀點,此次正好在這裏好好談談我對於這個問題的想法。緩存

咱們來看看 MVC 這種架構的特色。其實設計模式不少時候是爲了 Don't repeat yourself 原則來作的,該原則要求可以複用的代碼要儘可能複用,來保證重用。在 MVC 這種設計模式中,咱們發現 View 和 Model 都是符合這種原則的。網絡

對於 View 來講,你若是抽象得好,那麼一個 App 的動畫效果能夠很方便地移植到別的 App 上,而 Github 上也有不少 UI 控件,這些控件都是在 View 層作了很好的封裝設計,使得它可以方便地開源給你們複用。架構

對於 Model 來講,它實際上是用來存儲業務的數據的,若是作得好,它也能夠方便地複用。好比我當時在作有道雲筆記 iPad 版的時候,咱們就直接和 iOS 版複用了全部的 Model 層的代碼。在創業作猿題庫客戶端時,iOS 和 iPad 版的 Model 層代碼再次被複用上了。固然,由於和業務自己的數據意義相關,Model 層的複用大多數是在一個產品內部,不太可能像 View 層那樣開源給社區。mvc

說完 View 和 Model 了,那咱們想一想 Controller,Controller 有多少能夠複用的?咱們寫完了一個 Controller 以後,能夠很方便地複用它嗎?結論是:很是難複用。在某些場景下,咱們可能能夠用addSubViewController 之類的方式複用 Controller,但它的複用場景仍是很是很是少的。

若是咱們可以意識到 Controller 裏面的代碼不便於複用,咱們就能知道什麼代碼應該寫在 Controller 裏面了,那就是那些不能複用的代碼。在我看來,Controller 裏面就只應該存放這些不能複用的代碼,這些代碼包括:

  • 在初始化時,構造相應的 View 和 Model。
  • 監聽 Model 層的事件,將 Model 層的數據傳遞到 View 層。
  • 監聽 View 層的事件,而且將 View 層的事件轉發到 Model 層。

若是 Controller 只有以上的這些代碼,那麼它的邏輯將很是簡單,並且也會很是短。

可是,咱們卻很難作到這一點,由於仍是有不少邏輯咱們不知道寫在哪裏,因而就都寫到了 Controller 中了,那咱們接下來就看看其它邏輯應該寫在哪裏。

如何對 ViewController 瘦身?

objc.io 是一個很是有名的 iOS 開發博客,它上面的第一課 《Lighter View Controllers》 上就講了不少這樣的技巧,咱們先總結一下它裏面的觀點:

  • 將 UITableView 的 Data Source 分離到另一個類中。
  • 將數據獲取和轉換的邏輯分別到另一個類中。
  • 將拼裝控件的邏輯,分離到另一個類中。

你想明白了嗎?其實 MVC 雖然只有三層,可是它並無限制你只能有三層。因此,咱們能夠將 Controller 裏面過於臃腫的邏輯抽取出來,造成新的可複用模塊或架構層次。

我我的對於邏輯的抽取,有如下總結。

將網絡請求抽象到單獨的類中

新手寫代碼,直接就在 Controller 裏面用 AFNetworking 發一個請求,請求的完數據直接就傳遞給 View。入門一些的同窗,知道把這些請求代碼移到另一個靜態類裏面。可是我以爲還不夠,因此我建議將每個網絡請求直接封裝成類。

把每個網絡請求封裝成對象實際上是使用了設計模式中的 Command 模式,它有如下好處:

  • 將網絡請求與具體的第三方庫依賴隔離,方便之後更換底層的網絡庫。實際上咱們公司的 iOS 客戶端最初是基於 ASIHttpRequest 的,咱們只花了兩天,就很輕鬆地切換到了 AFNetworking
  • 方便在基類中處理公共邏輯,例如猿題庫的數據版本號信息就統一在基類中處理。
  • 方便在基類中處理緩存邏輯,以及其它一些公共邏輯。
  • 方便作對象的持久化。

你們若是感興趣,能夠看咱們公司開源的 iOS 網絡庫:YTKNetwork。它在這種思考的指導下,不但將 Controller 中的代碼瘦身,並且進一步演化和增強,如今它還支持諸如複雜網絡請求管理,斷點續傳,插件機制,JSON 合法性檢查等功能。

這部分代碼從 Controller 中剝離出來後,不但簡化了 Controller 中的邏輯,也達到了網絡層的代碼複用的效果。

將界面的拼裝抽象到專門的類中

新手寫代碼,喜歡在 Controller 中把一個個 UILabel ,UIButton,UITextField 往 self.view 上用 addSubView 方法放。我建議你們能夠用兩種辦法把這些代碼從 Controller 中剝離。

方法一:構造專門的 UIView 的子類,來負責這些控件的拼裝。這是最完全和優雅的方式,不過稍微麻煩一些的是,你須要把這些控件的事件回調先接管,再都一一暴露回 Controller。

方法二:用一個靜態的 Util 類,幫助你作 UIView 的拼裝工做。這種方式稍微作得不太完全,可是比較簡單。

對於一些能複用的 UI 控件,我建議用方法一。若是項目工程比較複雜,我也建議用方法一。若是項目太緊,另外相關項目的代碼量也很少,能夠嘗試方法二。

構造 ViewModel

誰說 MVC 就不能用 ViewModel 的?MVVM 的優勢咱們同樣能夠借鑑。具體作法就是將 ViewController 給 View 傳遞數據這個過程,抽象成構造 ViewModel 的過程。

這樣抽象以後,View 只接受 ViewModel,而 Controller 只須要傳遞 ViewModel 這麼一行代碼。而另外構造 ViewModel 的過程,咱們就能夠移動到另外的類中了。

在具體實踐中,我建議你們專門建立構造 ViewModel 工廠類,參見 工廠模式。另外,也能夠專門將數據存取都抽將到一個 Service 層,由這層來提供 ViewModel 的獲取。

專門構造存儲類

剛剛說到 ViewModel 的構造能夠抽獎到一個 Service 層。與此相應的,數據的存儲也應該由專門的對象來作。在小猿搜題項目中,咱們由一個叫 UserAgent 的類,專門來處理本地數據的存取。

數據存取放在專門的類中,就能夠針對存取作額外的事情了。好比:

  • 對一些熱點數據增長緩存
  • 處理數據遷移相關的邏輯

若是要作得更細,能夠把存儲引擎再抽象出一層。這樣你就能夠方便地切換存儲的底層,例如從 sqlite 切換到 key-value 的存儲引擎等。

小結

經過代碼的抽取,咱們能夠將本來的 MVC 設計模式中的 ViewController 進一步拆分,構造出 網絡請求層、ViewModel 層、Service 層、Storage 層等其它類,來配合 Controller 工做,從而使 Controller 更加簡單,咱們的 App 更容易維護。

另外,不知道你們注意到沒,其實 Controller 層是很是難於測試的,若是咱們可以將 Controller 瘦身,就能夠更方便地寫 Unit Test 來測試各類與界面的無關的邏輯。移動端自動化測試框架都不太成熟,可是將 Controller 的代碼抽取出來,是有助於咱們作測試工做的。

但願本文能幫助你們掌握正確使用 MVC 的姿式,在下一節裏,我將分享一下我對 MVVM 的見解。

被神化的 MVVM

MVVM 的歷史

MVVM 是 Model-View-ViewModel 的簡寫。

相對於 MVC 的歷史來講,MVVM 是一個至關新的架構,MVVM 最先於 2005 年被微軟的 WPF 和 Silverlight 的架構師 John Gossman 提出,而且應用在微軟的軟件開發中。當時 MVC 已經被提出了 20 多年了,可見二者出現的年代差異有多大。

MVVM 在使用當中,一般還會利用雙向綁定技術,使得 Model 變化時,ViewModel 會自動更新,而 ViewModel 變化時,View 也會自動變化。因此,MVVM 模式有些時候又被稱做:model-view-binder 模式。

具體在 iOS 中,可使用 KVO 或 Notification 技術達到這種效果。

MVVM 的神化

在使用中,我發現你們對於 MVVM 以及 MVVM 衍生出來的框架(好比 ReactiveCocoa)有一種「敬畏」感。這種「敬畏」感某種程度上就像對神同樣,這主要表如今我沒有聽到你們對於 MVVM 的任何批評。

我感受緣由首先是 MVVM 並無很大程度上普及,你們對於新技術通常都不熟,進而不敢妄加評論。另外,ReactiveCocoa 自己上手的複雜性,也讓不少人感受到這種技術很高深難懂,進而加劇了你們對它的「敬畏」。

MVVM 的做用和問題

MVVM 在實際使用中,確實可以使得 Model 層和 View 層解耦,可是若是你須要實現 MVVM 中的雙向綁定的話,那麼一般就須要引入更多複雜的框架來實現了。

對此,MVVM 的做者 John Gossman 的 批評 應該是最爲中肯的。John Gossman 對 MVVM 的批評主要有兩點:

第一點:數據綁定使得 Bug 很難被調試。你看到界面異常了,有多是你 View 的代碼有 Bug,也多是 Model 的代碼有問題。數據綁定使得一個位置的 Bug 被快速傳遞到別的位置,要定位原始出問題的地方就變得不那麼容易了。

第二點:對於過大的項目,數據綁定須要花費更多的內存。

某種意義上來講,我認爲就是數據綁定使得 MVVM 變得複雜和難用了。可是,這個缺點同時也被不少人認爲是優勢。

ReactiveCocoa

函數式編程(Functional Programming)和響應式編程(React Programming)也是當前很火的兩個概念,它們的結合能夠很方便地實現數據的綁定。因而,在 iOS 編程中,ReactiveCocoa 橫空出世了,它的概念都很是 新,包括:

  • 函數式編程(Functional Programming),函數也變成一等公民了,能夠擁有和對象一樣的功能,例如當成參數傳遞,看成返回值等。看看 Swift 語言帶來的衆多函數式編程的特性,就你知道這多 Cool 了。
  • 響應式編程(React Programming),原來咱們基於事件(Event)的處理方式都弱了,如今是基於輸入(在 ReactiveCocoa 裏叫 Signal)的處理方式。輸入還能夠經過函數式編程進行各類 Combine 或 Filter,盡顯各類靈活的處理。
  • 無狀態(Stateless),狀態是函數的魔鬼,無狀態使得函數能更好地測試。
  • 不可修改(Immutable),數據都是不可修改的,使得軟件邏輯簡單,也能夠更好地測試。

哇,全部這些都太 Cool 了。當我看到的時候,我都雞凍了!

咱們應該客觀評價 MVVM 和 ReactiveCocoa

可是可是,我忽然想到,我好象只須要一個 ViewModel 而已,我徹底能夠簡單地作一個 ViewModel 的工廠類或 Service 類就能夠了,爲何要引入這麼多框架?現有的 MVC 真的有那麼大的問題嗎?

直到如今,ReactiveCocoa 在國內外還都是在小衆領域,沒有被大量接受成爲主流的編程框架。不僅是在 iOS 語言,在別的語言中,例如 Java 中的 RxJava 也一樣沒有成爲主流。

我在這裏,不是想說 ReactiveCocoa 很差,也不是想說 MVVM 很差,而是想讓你們都可以有一個客觀的認識。ReactiveCocoa 和 MVVM 不該該被神化,它是一種新穎的編程框架,可以解決舊有編程框架的一些問題,可是也會帶來一些新問題,僅此而已。若是不能使好的駕馭 ReactiveCocoa,一樣會形成 Controller 代碼過於複雜,代碼邏輯不易維護的問題。

總結

有一些人老是追趕着技術,有什麼新技術無論三七二十一立馬就用,結果被各類坑。

又有一些人,老是擔憂新技術帶來的技術風險,不肯意學習。結果如今還有人在用 MRC 手動管理引用計數。

而我想說,咱們須要保持的是一個擁抱變化的心,以及理性分析的態度。在新技術的面前,不盲從,也不守舊,一切的決策都應該創建在認真分析的基礎上,這樣才能應對技術的變化。

相關文章
相關標籤/搜索