深入理解 MVC 中的 M 與 C

93發表於2018-05-02

理解 MVC 的關鍵:M 與 C

mvc2

對於 MVC 的理解,我發現爭論最大的是:如何理解 M 層與 C 層,即模型層與控制層間的關係以及各自承擔的職責。至於檢視層,在前端的開發中就顯得比較的「薄」。它主要包含了 HTML 和 CSS 檔案,負責搭建檢視靜態框架。雖然在 MVVM 的框架中,檢視層的職責略有不同,但這部分差異我們後面再談。

寫這篇文章的契機是由於我在實際的開發中發現:隨著專案的不斷壯大,控制層變的越來越臃腫,開發和維護的成本變的很高。這使得我逐漸開始質疑自己對 MVC 的理解。

起初,我對 MVC 中 M 的理解是:Model = 資料模型。

我當時認為 Model 指的就是「資料模型」或是「資料層」,該層負責封裝資料相關的邏輯,然後為控制層提供所需的介面。比如某個檢視的控制器需要獲取資料 A ,而資料 A 可能來源於本地快取、伺服器X、伺服器Y等多個資料來源。這部分邏輯的整合就會被封裝到某一個資料模型中,然後向外提供一個介面,供控制層呼叫。

如果是在一個資料複雜度較高的專案中,我上述對 Model 的理解雖然依然不能夠緩解控制層不斷膨脹的趨勢,但或許還能夠感受到些許好處。可是在我負責的專案中,雖然業務邏輯非常的多,但由於大部分的後臺介面都是定製介面,資料來源也比較單一,所以資料的複雜度並不高,Model 層只是封裝了一堆簡單的介面呼叫邏輯。Model 層過於的「薄」導致幾乎所有的業務邏輯都扔到了控制層中。

於是乎,我隱約感覺到:Model ≠ 資料模型。

經過深入的思考,我現在對 MVC 中 M 與 C 的理解分別是:

  • Model 指的是對同一類業務行為進行抽象而得到的業務模型,它與檢視層和控制層並沒有任何關係,它應該是獨立存在的。

  • Controller 負責事件的處理和各個業務模型間的排程,最後將檢視層需要的資料,也就是狀態(state),反應到檢視中。

mvc

舉個簡單的例子:假如我們正在開發使用者登入方面的功能,在架構的時候我們往往會設計一個使用者模型,裡面封裝了使用者登入和登出的行為,其核心邏輯分別是呼叫後臺的登入介面獲取 token 和將本地的 token 刪除。這部分的封裝很好理解,即使將 Model 理解為資料模型,我們也會這麼做。

除了登入和登出,我們還需要對使用者輸入的賬號和密碼的有效性進行提前校驗,比如:賬號必須是一個手機號,密碼至少是六位數。這部分需求分別對應兩個業務行為:使用者賬戶有效性的校驗和使用者密碼有效性的校驗。顯而易見,如果我們把 Model 看成是一個資料模型(即裡面只能封裝資料相關的行為),那麼上述兩個行為就只能寫在頁面對應的控制器中,而不是抽象到使用者模型中。

我們使用某一開發模式的初衷是為了減少開發的複雜度,使程式碼更容易被理解和管理。一個高質量的專案,即便是新來的同事,也能夠很容易的理解專案中程式碼的邏輯並快速的投入到開發工作當中。

可以想象,當我們在翻閱使用者模型的程式碼時,居然找不到使用者賬號和密碼校驗相關的程式碼塊,而是需要在登入頁面的控制器中才能找到,這種不適的感覺,我相信你能夠感受的到。如果感受不到,我舉個現實生活中的例子你就能有所體會了。

假設你去攝影店裡拍證件照,但是他們最終給你的是一張未經剪裁的大照片,你叫他們給你剪裁成六張單獨的照片,但是店家卻說:我們不提供這種服務。。。雖然,我們家裡也有剪刀,可以在家自己剪,但是這事兒不應該是店家的「本職工作」嗎?

Q&A

1. 如果是把所有的業務邏輯都放在 M 層,那麼 M 層會不會也變得非常臃腫?

需要注意的一點是,一個頁面一般只會有一個控制器,如果這個頁面的業務邏輯越來越多,要是把這些業務邏輯都放在控制器中,控制器顯然會變的越來越臃腫。但是把業務邏輯都抽象到 M 層就不一樣了,因為我們是將所有的業務邏輯分別抽象到多個業務模型中,也就是說 M 層中包含了多個業務模型。隨著專案體量的增加,雖然部分業務模型的程式碼量也會增加,但由於模型本身已經是高度的抽象了,所以並不會過多的增加程式碼複雜度,其實更多隻是增加了業務模型的數量而已,M 層只會越來越飽滿,而不會顯得臃腫。

2. 如何理解 MVC 與 MVVM 的關係?

  1. MVVM 是在 MVC 基礎上的優化,我對它的理解是:由於 MVVM 框架提供了資料雙向繫結的能力,使得控制層不用操心如何將資料反應到檢視中,而只需要維護那些繫結在檢視上的資料既可以,從而進一步減少了控制層的複雜度。

  2. MVVM 與 MVC 中的 M 的概念是一致的,指的都是業務模型。

3. 如果某一業務邏輯與檢視的耦合度很高,是否還需要抽象到對應的業務模型中?

在實際的開發中,我們會發現許多的業務與檢視的耦合度很高,這時我們總是會糾結是否將這部分耦合度很高的業務邏輯抽象到具體的業務模型中,因為這部分邏輯很難被其他地方複用。我的看法是:

抽象業務模型的初衷不是為了複用,而是為了方便管理。

比如在 A 頁面和 B 頁面都有一個 a 行為,但是在兩個頁面中,a 行為的實現方式完全不一樣,且對頁面都有著極高的耦合度。如果該行為對應的業務模型是 M ,我的做法是:將兩個行為都抽象到 M 中,其對外的介面名分別是:aForPageAaForPageB ,而不是因為其極高的耦合度就程式碼解除安裝頁面對應的控制器中。這樣做得好處是我們將理應屬於 M 模型的行為都抽象到了該模型中,使得程式碼非常的容易被理解和管理。而對於那些耦合度較高的行為我們可以為其定製更加語義化的介面名,以方便其他同事的理解與後續的重構。但需要注意,假如專案的業務邏輯並不複雜,或者上述的 M 模型並不存在時,我們沒有必要為這些高耦合的業務行為抽象出一個業務模型 M ,這樣會產生不要的複雜度。在這種情況下,將那些業務行為寫死在控制器中或許是更好的選擇。

總結

  1. MVC 中的 M 指的是業務模型而非資料模型。
  2. 理想情況下,所有的業務邏輯都應該抽象 Model 層。
  3. 理想情況下,控制層只是負責響應檢視的事件和各個業務模型間的排程。
  4. 具體情況具體分析,若是嚴格的遵循2、3兩點也有可能增加專案不必要的複雜度,事事無絕對。一切都以降低專案複雜度為最終目的。

相關文章