我與MVVM的恩怨情仇
記得面試那會兒,因為有一點MVVM的使用經驗,跟面試官談了一下。我的觀點是,ViewModel封裝展示邏輯,不包含介面呼叫,介面呼叫封裝在另一個類裡面(比如xxx API),然後在controller呼叫API類拿到資料後由ViewModel封裝好給到檢視層。當時得到的評價是,這不算是MVVM,還是在對Controller瘦身的層次上。
來到公司後發現,這裡的MVVM是在ViewModel裡包含了網路呼叫,讀寫快取,業務邏輯,展示邏輯等等,幾乎是Controller裡面抽取了UIView,剩下的所有邏輯的集合。特意搜尋了一些iOS MVVM的文章,的確是把眾多邏輯都放在ViewModel裡。但是一直覺得這種做法只是把臃腫從Controller移到了ViewModel,而且明顯違背單一職責原則,ViewModel變成一個大雜燴。
今天看到一篇文章,MVVM 不是那麼好,觀點大致是:ViewModel引入太多的邏輯,MVVM只是轉移和緩解了MVC的問題,無法徹底解決。討論MVVP的文章看了不少,其中不乏抨擊MVVM的,但這篇直入我心扉,也給了我不少啟發。忍不住吐槽一下現在專案的MVVM。
現在的MVVM有什麼問題?
- ViewModel職責太多。號稱MVVM分離了UI和邏輯,但一個應用只分離UI和邏輯是遠遠不夠的,”邏輯”裡往往會有很多”子邏輯”,這裡子邏輯現在全部堆在ViewModel裡,造成了一個萬能的ViewModel。導致一個ViewModel可能有非常多的狀態和介面,遲早變成變成Massive ViewModel,重蹈Controller的覆轍。
- MVVM試圖把UIViewController跟UIView看成同一個東西。Controller知道所有的View,它能響應整個頁面檢視的事件,在檢視響應鏈裡扮演重要角色,Controller有生命週期函式,業務層的呼叫入口往往是在這裡。這些特性使Controller理所當然地充當膠水程式碼。這些特性即無法轉移,也是獨一無二的。所以Controller註定跟UIView不一樣。即便在MVVM中Controller也負責ViewModel的生命週期管理。所以想把膠水抽到ViewModel中,把Controller薄化成UIView往往是不徹底的。
- 難以測試。最近在部門在推單元測試,由於之前有一些單元測試經驗,毛遂自薦做了一次推動者。發現有些ViewModel測試無從下手,原因是狀態複雜,內部邏輯太多。現在定義的ViewModel是封裝邏輯提供給Controller使用,天生會積極隱藏Controller用不到的邏輯。按照測試原則,只測公有方法,但是內部又有一堆關鍵的邏輯。
他山之石,可以攻玉,看看其它語言和框架的MVVM是怎樣的。
AngularJS 2.0版本聽說大變樣了,這裡只說2.0以前的版本。AngularJS的MVVM跟iOS傳統的MVVM大不一樣,網路呼叫往往是封裝在一個Service裡面,並不包含在ViewModel,ViewModel($scope)非常薄,主要用來做資料繫結和事件轉發。用AngularJS的經驗不多,但是在一兩個專案實踐中,感覺AngularJS的MVVM比iOS傳統的MVVM劃分更合理一些。
那麼,MVVM究竟該怎麼玩?我的答案是:不必拘泥於既有的模式。
記得某大神說過,架構就是不停地拆分。拿到需求後,拆分成各個子功能,對應App各個子模組,然後子模組再細分,接著整理依賴關係,上浮下沉,自然就形成了分層。所以設計最關鍵的,不是拘泥於應用MVC或者MVVM或者MVP,而是學會拆分,把職責分離,形成高內聚的模組,並設計鬆耦合的介面。其中的利器便是SOLID原則,個人最常考慮的是S(單一職責)和O(開閉原則),能把這兩點做好,已經很可貴,正在努力攻關中。。。
重新思考了一下App架構大致的模樣,細化後的模組,大致都可以被劃分成幾種型別,比如很多功能都需要一個網路請求的子模組,一個加工資料的模組。總結了一下,大致會有以下幾種:API,Store,LogicModel,ViewModel,Controller,View。API封裝網路,Store封裝本地持久化,LogicModel封裝業務邏輯(記憶體操作,跟後端架構中的領域層類似),ViewModel封裝展示邏輯,Controller的職責還是膠水,它可能會持有各種物件,並負責協助物件通訊。這些應該是大多數功能模組劃分時都需要考慮的子模組。至於這麼劃分後還是MVVM嗎?不要再思考這個問題,程式設計是一個創造性的活動,就像招術不是武術的根本,MVVM,MVP,MVC也不是設計的根本,設計原則才是。