前言 前一階段對MVC模式及其衍生模式做了一番比較深入的研究和實踐,這篇文章也算是一個階段性的回顧和總結。 經典MVC模式 經典MVC模式中,M是指業務模型,V是指用戶界面,C則是控制器,使用MVC的目的是將M和V的實現代碼分離,從而使同一個程式可以使用不同的表現形式。其中,View的定義比較清晰, ...
前言
前一階段對MVC模式及其衍生模式做了一番比較深入的研究和實踐,這篇文章也算是一個階段性的回顧和總結。
經典MVC模式
經典MVC模式中,M是指業務模型,V是指用戶界面,C則是控制器,使用MVC的目的是將M和V的實現代碼分離,從而使同一個程式可以使用不同的表現形式。其中,View的定義比較清晰,就是用戶界面。但對於Model和Controller的定義則較為模糊,以致在項目實踐中對它們的職責產生了很多不同的理解。其中比較主流的有下麵兩種。
1、閉環黨
比較傳統,問題是Model和Controller職責不清,在實操時容易走形
2、 開放派
MVP的前身,問題是Controller職責太重,優點是View和Model沒有了直接的關聯
對MVP的一點淺見
如果我們希望View和Model脫離關聯的話,那麼很容易就會使得所有的職能都落到Controller頭上,就如同上圖所示。這樣,Controller也就變成了Presenter,MVC也正式演化為MVP。所有的數據都由Presenter來驅動,所有的業務邏輯也由Presenter來實現。
MVP模式常見於Android,是谷歌官方推薦的App設計模式。從我找到的這張圖上,可以非常明顯地看出三者之間的關係。
Model只是一個數據通道,退化成了Repository。如果將Model擴而大之,那麼就會變成下麵這張圖描述的場景:
Model搖身一變,成了一個數據交換中心。
MVP模式的優點是實現了View和Model的解耦,缺點是Presenter職責太重。
對MVVM的一些理解
MVVM模式現在非常流行,下麵這張圖描述了MVVM模式各部分的關係和職能:
MVVM模式同樣實現了View和Model的解耦。不過和MVP模式不同的是,MVVM是從數據驅動的角度出發來解決這個問題的。ViewModel和View實現雙向數據綁定,ViewModel的數據變化自動地反應到View上。這樣,ViewModel就代表了View,成了一個Agent。ViewModel也承擔一點業務邏輯,但非常有限(也有把大量業務邏輯放在ViewModel裡面的做法,這個就和MVP沒什麼本質區別了)。所以,業務邏輯的職能就只能由Model來扛了。事實上,MVVM模式本質上是MVC模式去掉了Controller或者說是Model合併了Controller。
MVVM模式的優點是雙向數據綁定帶來的便利,Model完全不需要關心View。以數據為核心的視角非常新穎,並且在處理業務邏輯上也更加直觀。缺點其實和MVP一樣,只不過它是Model臃腫而已。
我們為什麼需要MVC?
MVC、MVP、MVVM(這裡把它們統稱為MVC),這種模式的真正好處是什麼?說實話,這個問題好思考了很久,腦海裡閃過的答案總覺得似乎還差那麼一點。最終,梳理出以下幾點,和大家探討:
專業分工的需要
程式員和設計師的技能是完全不同的,用戶界面就應該由設計師來完成而非程式員。所以,我們需要一個不包含任何邏輯代碼的View,以便由設計師來完成用戶界面的創建工作。程式員則可以專心去做和邏輯、數據相關的工作。在這個層面上,不需要考慮讓人糟心的Model/Controller還是Model/Presenter或者Model/ViewModel如何劃分職責的問題。View的分離顯然非常成功!
應對變化的需要
既然用戶界面的問題已經得到了完美解決,那麼,就該輪到業務邏輯和數據處理的問題了。需求總是在不斷變化,程式猿對於產品狗的敵意就來自於不斷地更改需求。於是,少改、好改就成了程式員的永恆目標。行之有效的套路其實也就是分層解耦而已。無論是Model/Controller還是Model/Presenter或者Model/ViewModel,不過是分層的角度和方法不同而已。
流程工藝的需要
在軟體工程領域,規範提得很多,流程提得很少,工藝幾乎沒有人提過。但在傳統的製造業和工程施工領域,最核心的就是流程和工藝。流程和工藝,是工業化生產的組織和產品品質的基礎。
在軟體工程上,代碼風格規範就是一種工藝,瀑布式開發或者敏捷開發都是流程。如何劃分Model和Controller的職責,是軟體的一個設計過程,也屬於工藝的範疇。三者之間如何依賴,其本質是一個流程問題。被依賴的一定是先於依賴者被生產出來。明確了職責劃分和依賴關係,才能科學地編製開發計劃,保證產出的代碼的品質和效率。
有『套路』可循,我們做起事情來總是會簡單快捷許多。
傳統MVC模式存在的問題
我們知道,經典MVC模式早先的主要問題是Model和Controller的職責不明,但現在,主要問題是無法進行單元測試。既然已經知道問題在哪裡,那麼,我們來想辦法解決問題就好了,但這之前,還需要解決幾個別的問題。
業務邏輯、界面邏輯和數據邏輯傻傻分不清
從廣義上講,無論是點擊按鈕打開一個對話框,還是撥動一個開關切換界面樣式,或者驗證輸入數據的合法性,都是業務邏輯,並沒有什麼必要分得那麼細。從某種意義上,把業務邏輯分為內在的、直接的反應,和需要用戶進一步操作的,狀態不確定的過程,可能更加有用。前者例如分頁顯示的列表用戶點擊了下一頁按鈕,從而產生了刷新數據的指令;後者例如用戶點擊了編輯按鈕,是否會修改數據,修改成什麼數據在這個時候都是未知的。
三者之間如何依賴
這是一個大問題!經典的VMC模式中,View是依賴於Model的。但我個人的理解是View是需求的最直接的體現,所以View應該是先驗的存在,應該是Model依賴View而不是相反。在實際的項目中,在確定原型後,設計師和負責介面的程式員會同時開始工作。同時,測試工程師也會開始編寫測試用例和單元測試代碼。他們的工作依據都是產品給出的原型。
等一個View編寫完成後,依賴於這個View的Model就可以開工了,每一個View都會對於著一個Model和若幹的介面。最後,就輪到依賴於若幹Model的Controller登場了。這樣做的好處是整個開發過程是由底向上、由表入里的,整體過程非常自然,而且結構簡潔明瞭。
如何劃分Model和Controller的職責
這是一個更大的問題,足以引起開發者的爭吵不休,就如同什麼語言最好一樣。
拋開這些分歧,我們就會看到,無論是什麼模式,它所需要解決的無非是業務邏輯和數據問題!所以,我認為問題的本質不是誰負責什麼,而是如何分離業務邏輯和數據。
特定的用戶界面需要特點的數據,有著特定的業務邏輯。這個前提之下,解耦是沒有意義的。我們要求的職責分明,無非是為了更容易應對變化。那麼,都有哪些變化呢?
- 界面樣式變了,但業務邏輯和數據都沒有變
- 界面樣式和業務邏輯變了,但數據沒有變
- 界面和數據變了,但業務邏輯沒有變
- 界面和數據沒有變,但業務邏輯變了
- 全都變了
界面的改變可以分為兩種,一是樣式改變,這種變化無關其他;另一種是元素變化,必然對應著數據的變化,需要修改介面。另外,就是業務邏輯的改變,而業務邏輯和數據並不存在必然關係。在MVC模式下,View的數據來源於Model,那麼,Model的職責就是負責View和介面之間的數據交互,起一個數據通道或者說是數據引擎的作用。當數據發生變化的時候,只需要修改Model即可。
既然Model承擔了數據引擎的職責,那業務邏輯就應該由Controller來承擔。同時,因為不同的View之間也會存在交互,那麼,也需要一共同的個中間人來進行轉接和調度,由於Model和View是一對一的綁定關係,並不適合承擔這個責任。所以,Controller負責業務邏輯是天然的。不過,那些和數據直接相關的事件,例如改變了一個選項後引起可用數據的變化、切換分頁載入新數據之類的和具體業務沒有關係的簡單反應型的業務邏輯,我覺得交給Model去實現更簡單和直接,並不一定要經過Controller去驅動Model。
在這種模式下,Model負責數據,Controller負責業務邏輯,整體是非常協調的。反過來看MVP和MVVM,因為迴避職責的劃分的問題導致了Presenter或Model的臃腫。
View和Model要不要雙向數據綁定
非常有必要!傳統的MVC是沒有雙向綁定的,這樣,View上面數據的變化就必須通過Controller去修改Model。而建立雙向綁定後,Controller就無需承擔這個職責了,從整體上看,職責更加分明,邏輯也會更加簡單。
改進的MVC模式
解決了以上四個問題,我們可以得到這樣的一個新的MVC模式:
這種改進模式,相對傳統的MVC模式,解決了職責不清的問題。相對於MVP和MVVM而言,沒有因迴避職責劃分問題導致的龐大而混亂的Presenter/Model。在僅僅是View樣式不同的場景下,Model是可以復用的。而使用哪個View,可以通過重載Model的構造函數來決定。事實上,即使View的元素不同造成數據不同,Model也可以利用泛型等技術手段來達到重用代碼的目的。
因為View並不依賴任何人,所以,我們可以很方便地把View替換成單元測試代碼(View本身是可根據場景需要相互替換的),只要騙過Model就OK。這個測試類一旦被Model構造出來,就會自動驗證數據、模擬用戶更新數據和發出指令。
在項目中展開的話,結構如下圖:
後記
這不是結束,而是一個開始……