理解并運用MVC,MVP,MVVM
MVC,MVP and MVVM
MVC
MVC 是一種 GUI 程序架構 模式,其目的是分離關注點,通過將程序按照不同的功能拆分為不同的層級來實現,又稱為“分層架構”,具體的做法就是將程序拆分為負責數據存取的“模型”(Model)、負責用戶界面的數據展示和響應用戶交互“視圖”(View)和負責模型和視圖間進行傳遞數據的“控制器”(Controller)三層。
淺談分層架構
分層架構是指將應用程序按照不同的功能將程序拆分為多個層次,每個層次負責實現自己的功能并開放 API 供其它層次調用其功能。各層按照一定的規則通信,通常是上層依賴下層,下層根據上層輸入返回數據,這樣單向且順序的依賴關系。
在 GUI 程序的分層架構設計中,通常分為表示層(Presentation Layer)、應用層(Application Layer)、數據訪問層(Data Access Layer)和數據層(Data Layer)。各層次之間的職責和依賴關系如下:
- 表示層
負責用戶界面的數據展示和響應用戶交互,是程序的入口,即分層中的頂層。
表示層依賴應用層,調用其功能獲取數據用于展示或將用戶輸入的數據保存。 - 應用層
接收表示層傳遞來的數據,對其進行檢驗、處理等操作;
應用層依賴數據訪問層,調用數據訪問層的功能,并將表示層傳遞來的數據處理后發送給數據訪問層,在數據訪問層返回數據后,將返回的數據處理后回傳給表示層; - 數據訪問層
負責的數據的讀取與更新等操作。
數據訪問層依賴數據層,它將應用層傳遞來的數據處理后傳遞給數據層以操作數據,并將數據層返回的操作結果返回給應用層。 - 數據層
負責數據源的維護,包括數據建模、數據存儲,一般是和數據庫軟件、緩存軟件、文件系統等負責數據持久化的基礎服務交互。
它依賴數據庫等外部軟件提供的第三方模塊來與外部軟件實現交互,比如與 MySQL 交互,Java 程序依賴jdbc.jar,python 程序依賴pymysql模塊,PHP 程序依賴MySQLi或PDO擴展, 運行環境為 Node.js 的 JavaScript 程序依賴mysql模塊。
數據層負責維護數據源的軟件本就提供數據的讀取與關系的功能,如果程序選定好了依賴某個軟件提供數據而不再變動,可以合并最后兩層為數據訪問層,稱為三層架構。
但是如果為了程序的拓展性,希望程序可以使用不同的第三方軟件提供數據服務,那數據訪問層可以為應用層消除不同第三方軟件交互方式的差異。具體做法就是抽象數據訪問層的 API,不同的第三方軟件使用不同的實現,不同實現的接口保持一致。
上述的各層級間的關系,和“隊列”這樣的線性數據結構中各元素的關系是一樣的,又因為每一層都執行特定的任務,所以可以說單向且順序的分層架構是一種任務隊列。
順序依賴還有一個特點,就是數據傳遞的過程是固定的,稱為單向數據流,很容易追蹤數據,方便了程序的調試和測試。
當然了,各個層級之間采用雙向依賴、跨層依賴的關系也不是不行,這無非使各層次的依賴關系變得復雜。順序依賴的關系被打破,數據的傳遞方向變得多樣,數據難以追蹤,不便于程序的調試與測試。
MVC 往往不是單向且順序依賴關系的分層結構
如果視圖層到控制器層,再到數據層是單向的依賴關系,視圖響應用戶輸入,轉發給控制器,控制器對輸入進行處理,轉化為對數據模型的操作,再將操作數據模型的結果轉化為視圖需要的數據回傳給視圖,視圖根據回傳的數據作出更新,模型不會主動向視圖或者控制器推送消息,這樣的 MVC 被稱為被動 MVC。
當一個視圖表現一個模型數據的時候,這沒有問題;但模型數據作為程序內多個視圖模塊間共享的數據源的時候,一個視圖修改了數據數據,其它的視圖無從得知,直到其它再次主動讀取數據后才知道數據發生了變化。數據發生改變與其它視圖主動讀取數據獲得最新狀態兩個行為之間存在時間差,這個時間差會導致其它視圖展示的數據與數據源之間存在數據不一致的情況。
如果嘗試在修改了數據的視圖模塊中通知其他視圖更新,看起來解決了數據不同步的問題,但是如果將來有更多視圖,這些視圖之間相互影響,每個視圖要知道其他所有視圖,然后在程序中任何改變了數據狀態的邏輯之后都要添加數據同步的邏輯。
如果新增或移除了視圖模塊,或改動了視圖模塊的 API,要仔細地翻閱各個視圖模塊的代碼,所有依賴這些模塊的其他模塊都要更改,一旦遺漏,程序將不能按預期執行。
這樣復雜的依賴關系難以維護,應該將這些數據同步的邏輯轉移到同一個地方,控制器層和數據層都可以,但是數據層是數據狀態發生改變的地方,轉移到這里更簡單。
這也就意味著模型要直接依賴視圖,以實現在數據狀態改變時通知視圖更新,但視圖不應依賴模型,視圖與模型之間數據的處理與傳遞仍由控制器負責。這種模型主動通知視圖更新的 MVC 稱為主動 MVC。
雖然將數據同步的邏輯轉移到了一處,比由視圖通知視圖的方式更容易維護,但是還是有些麻煩,模型需要知道所有希望響應它的狀態的視圖,以及通知視圖更新的 API,當添加或移除視圖后,還要去模型增減相關邏輯代碼。
在面向對象編程中,可以使用觀察者模式來解決這個問題,定義額外接口,通過實現接口的方式來實現數據同步的邏輯,可以保持模型和視圖的相對獨立。
模型實現觀察者模式中的主題接口毋庸置疑,但 View 與 Controller 都可以是觀察者,甚至二者同時作為觀察者響應模型的數據變化。
使用觀察者模式引入了新的角色,使得 MVC 各層次之間從直接依賴改為了間接依賴,從依賴具體實現改為了依賴抽象。但要實現數據同步,就無法完全消除依賴關系。
誰實現觀察者接口決定了 MVC 各層間的依賴關系,如果由 Controller 做觀察者或 View 與 Controller 同時做觀察者,就成了 MVC 的特定變體 MVP。
MVP
MVP 中 P 是 Presenter,也即 MVC 的 Controller。MVP 指 Presenter 做觀察者,負責響應數據變化并更新視圖的數據同步邏輯,MVP的特點是 Passive View,被動視圖意味著 View 不會改變自身狀態,完全由 Presenter 操作,意味著 View 要定義許多更新視圖的 API 供 Presenter調用。
MVP 架構中各層通按以下方式通信:
- 當視圖接收到用戶輸入時,轉發給 Presenter 進行處理;
- Presenter 根據用戶輸入對 Model 進行操作,在需要時讀取數據或更新數據;
- 當 Model 層數據變動時,可以通知觀察者 Presenter;
- 當需要更新視圖時 Presenter 通過視圖提供的 API 更新視圖;
Supervising Controller
監督控制器指 View 與 Controller 同時做觀察者,都負責數據同步工作,但是側重點不同。
將簡單的同步操作放在 View 中實現,比如要同步的數據類型一致或者只要做簡單的類型轉換以及計算;對于涉及到復雜計算的同步操作應在 Prennter 中實現。
這種方式適合有大量的數據同步需求的項目,相比 MVP,Controller 的負擔減輕了,但增加了 View 的復雜程度。
MVVM
MVC 和 MVP 本質上是一樣的,只是組織代碼的方式有差別。而 Model-View-ViewModel(MVVM) 也是將程序分為三個層次,其中數據同步的功能由 ViewModel 完全負責。
相比 MVC,MVVM 引入了新的概念:
- 分離視圖
- 聲明式視圖
- 視圖狀態
- 數據驅動視圖
從業務邏輯中分離視圖
視圖的功能是顯示數據,響應用戶輸入,它的職責僅此而已。但是它卻往往是任務繁重,在業務代碼中的占據著不少的比重。我們為視圖定義數據,定義響應用戶輸入的邏輯,本質上是重復勞動。
既然是定義一些東西,完全可以在聲明視圖結構的時候就一并給定描述。然后專門寫一個公用的邏輯,對視圖的數據進行綁定,并實現數據的同步功能。
聲明的意義在于描述一個事物,不論它是否存在;而定義的意義在于明確一個已經存在的事物。聲明是定義的前提,這也是為什么說聲明一個變量,不賦值就是空或默認值,而將一個值賦予一個變量被稱作定義變量為該值。
聲明式視圖與視圖狀態
視圖狀態指的是視圖中可能會變化的數據以及視圖響應用戶輸入的行為。
聲明式視圖指的是使用特定格式的文本,或者說標記語言來聲明視圖,在聲明中為視圖綁定數據源,綁定狀態(數據和行為),然后寫一個解析器來生成代碼。
下面的偽代碼解釋了這個過程:
<Card data="NameCard"> // 綁定視圖數據源
<TextView>{name}</> // 綁定狀態(即數據源的屬性)
<TextView>{tel}</> // 綁定狀態
<Button click="{call}">Call me</Button> // 綁定行為
</Card>
解析器接收聲明視圖的標記文本和數據源,將不同的標記解析為不同的控件,并使用數據源為其初始化數據,綁定響應用戶輸入的處理邏輯,實現數據源狀態和視圖狀態之間的數據同步邏輯。
不同的開發平臺有不同的實現,比如 Javascript Web 應用使用 HTML 或者 JSX 聲明視圖,Android 使用 XML 或 Compose 來說明視圖,而微軟的 WPF .NET 使用 xaml, Blazor 使用 razor。
使用標記語言來聲明視圖,再通過調用解析器來生成代碼的方式定義視圖,真正做到了將視圖的關注點從業務邏輯中分離。
在定義視圖的同時傳入數據,又為視圖的狀態自動綁定了數據,還自動實現了視圖與狀態間數據同步的邏輯。
解析器最后生成的負責視圖和數據同步的代碼就是 ViewModel,它在運行后,往往是返回一個與傳入數據結構相同的數據對象,通過操作該對象可以獲取或更新視圖的狀態,我們稱之為響應式數據。
數據驅動視圖
數據驅動視圖的直觀表現是,改變響應式數據,視圖就更新,視圖輸入改變控件的狀態,響應式數據也隨之變化。其原理是 ViewModel 維護一個數據集合,與 View 的狀態一一對應。本質上還是觀察者模式,View 通過觀察 ViewModel 的數據變化,來更新視圖。而 View 因用戶輸入產生的狀態變化也會主動更新到 ViewModel 的數據集合中。
需要注意的是,ViewModel 名字里的 'Model' 代表的是 View 的狀態,是前面提到的響應式數據,它也不簡單的是解析器生成 ViewModel 代碼時傳入的數據,那只代表這個 'Model' 的結構和初始值,View 狀態變化,會同步到與該 'Model' 中對應的屬性。而 MVVM 中的 Model負責的是數據持久化,它與 ViewModel 中的 Model不一樣。
通過更改 ViewModel 中的 Model 的數據就可以更新 View 的顯示,就是數據驅動視圖。
一切皆數據
數據驅動視圖的開發方式已經是所有追求先進性的 GUI 軟件開發框架所必備的了。
運行 ViewModel 的代碼,其返回值中是不包含 View 的細節,能操作的只有 View 的狀態所對應的響應式數據。在開發過程中可以完全當視圖不存在,一切都是數據。

浙公網安備 33010602011771號