GUI 架構簡述

本文從細節入手,嘗試分析了幾種常見的 GUI 架構:MVC、MVCS、MVP、MVVM。對於在實際開發中如何選擇給出了一些參考意見。html

本文同時發表於個人我的博客react

Overview


移動開發架構,不管是 iOS、Andriod 仍是 Web 都屬於 GUI (Graphical User Interfaces) 架構範疇。2006年,Martin Fowler 的 GUI Architectures 一文可謂是經典之做。文中 Martin Fowler 提到 MVC 模式如何組織代碼、劃分模塊職責,還提到 Data BindingFlow Synchronization 以及 Observer Synchronization 等核心概念。 縱觀十年來 GUI 架構演變,不管是 MVCS、MVP 仍是 MVVM,其實討論的核心問題仍是如何分層、如何劃分模塊職責、作好代碼隔離。ios

談到 iOS 上常見架構,相信只要有半年以上開發經驗的同窗都能侃侃而談。但在實際交流過程當中發現很多同窗對關鍵細節問題卻認知模糊,甚至是錯誤的。所以,本文嘗試從細節入手對幾種常見架構進行簡單描述(對架構的認識智者見智、仁者見仁,我所描述的也不必定是正確的)。git

爲何要分層


上文提到各類架構雖各類不一樣,但它們其實都是在討論一個問題:『如何分層』。 那麼在繼續以前,咱們有必要思考一下:爲何要分層?github

計算機界有一句大道至簡的名言:設計模式

All problems in computer science can be solved by another level of indirection.安全

之因此要分層,最終目的是下降系統總體複雜度。 經過分層咱們至少能得到如下能力:網絡

  • 提供良好的抽象,隱藏實現細節,下降耦合度;
  • 隔離變化;
  • 提升模塊可複用性;
  • 加強系統可擴展性。

說到分層,可能最早想到的例子是 OSI 七層或 TCP/IP 五層網絡模型: 架構

在分層網絡模型中,不一樣協議工做在不一樣網絡層,互不干擾,又協調有致,如:IP 協議工做在網絡層,主要職責是網絡尋址;TCP 協議工做在傳輸層,主要負責創建可靠的網絡鏈接、負責擁塞控制等。正是由於良好的分層,IP 協議無需關心 TCP 協議的工做,反之亦然。同時 IP 協議也能夠在 TCP、UDP 協議間複用。

現在對網絡安全愈來愈重視,HTTPS 協議也慢慢普及,經過分層,只需在原有 HTTP 協議基礎上添加一個 TLS/SSL 的安全傳輸層便可,由它來負責加解密,而原有的 HTTP、TCP 協議無需修改: app

Model-View-Controller


MVC(Model-View-Controller)做爲最經典的架構,廣爲人熟知,也是 Apple 官方推薦的移動架構。

MVC模式的核心思想是數據層(Domain)與表現層(Presentation)的隔離。

Separated Presentation: Ensure that any code that manipulates presentation only manipulates presentation, pushing all domain and data source logic into clearly separated areas of the program.

那麼,在數據與展示被隔離以後,它們之間如何同步數據、狀態? 這就涉及 MVC 模式另外一個重要思想:觀察者同步(Observer Synchronization)。

Observer Synchronization: Synchronize multiple screens by having them all be observers to a shared area of domain data.

經常使用方法:在Presentation Object(Controller)中註冊通知、設置delegate、傳遞block等。當數據須要更新時,Domain Object(Model)經過上述方式將數據自底向上的同步給Presentation Object。

下面簡單介紹一下 Model、View、Controller:

Model


Apple: Model Objects Encapsulate Data and Basic Behaviors. Stanford: Model = What your application is (but not how it is displayed). 簡單講:Model = Data + Manipulate Data

(ps:本文中的Stanford表示斯坦福的 iOS 公開課)

如:咱們書架的 Model: QRBookShelfModel,包含了數據: NSArray<QRBookShelfItem *> *books以及對數據的操做: addBook:deleteBook:等。

Controller


Apple: Controller Objects Tie the Model to the View. Stanford: Controller = How your Model is presented to the user(UI logic).

Controller 是 Model 與 View 間的鏈接器,其核心職責有:

  • 處理用戶事件;
  • 處理展現邏輯;
  • 鏈接 Model 與 View。

這裏有個問題:到底什麼是展現邏輯? 簡單講:將業務數據轉換成UI數據,如:

  • 下載進度,從 Model 層返回的是 double 型,將其轉換成可展現的 string 類型(0.811—>81.1%);
  • 性別,從 Model 返回的是0、1這樣的 int 型,將其轉換成:1->男,0—>女;
  • 日期,將時間戳格式化:123456789923—>2016-07-01 10:09.

View


Apple: View Objects Present Information to the User Stanford: View=Your Controller’s minions

總之,View 只作一件事:layout。

MVC 規則


爲了實現 MVC 的核心思想:業務 (model) 與展現 (View) 的隔離,必須嚴格遵照一些規則:

  • Controller 依賴(持有) Model、View(可直接與它們通訊);
  • Model 與 View 互不可見(不可通訊);
  • View 只負責layout,且不能保存業務數據 (須要數據時經過 datasource 方式向 Controller 要);
  • View 可經過 target、delegate 與 Controller 同步狀態;
  • Model 不能主動與 Controller 通訊,經過 Notification、KVO、delegate、block 等機制通知 Controller 數據變化。

看到這裏,你們有沒有一種熟悉的味道? 沒錯,UITableView 與外界 (Contoller) 的交互與此處的描述高度一致。

(關於 MVC 規則的描述,你們也能夠參考 Stanford iOS 公開課中的相關內容)

有問題嗎?


此時,你們或許心中有些疑問: 一、在 MVC 模式中,網絡請求、數據存儲誰來完成? 二、Model、View、Controller 誰的可複用性最強? 三、展現邏輯爲何由 Controller 完成而不是 View?

  • 在 MVC 模式中,網絡請求、數據存儲誰來完成? Controller、Model 均可以,通常由 Model 完成。此時的 Model 已再也不是簡單的 Model Object,而是 Model layer,在咱們項目中一般將其稱爲 Manager。

  • Model、View、Controller 誰的可複用性最強? View>Model>Controller

  • 展現邏輯爲何由 Controller 完成而不是 View? View可複用性高,不該關心具體展現邏輯,只專一於 layout

Massive View Controller


MVC 模式被批評最多的就是 Controller 過於臃腫,那麼 Controller 都作了什麼?

  • 處理複雜的展現邏輯;
  • 處理用戶事件;
  • 初始化 View、管理部分 View 的生命週期並提供數據;
  • 處理業務數據變化,轉換爲 UI 結果;
  • 獲取、存儲數據(可選)。

尤爲是現在不少產品經理『擅長』作加法,頁面、交互愈來愈複雜,這對於 Controller 來講無疑是雪上加霜。

Model-View-Controller-Store


前面提到,在 MVC 模式中,並無討論獲取數據屬於哪一個模塊的職責 (通常由 model 負責)。MVCS 模式就是在 MVC 基礎上將數據單獨提取爲一層(Store)。

Model-View-Presenter


在 MVC 模式中,展現邏輯被劃分爲 Controller 的職責範圍。現在,展現邏輯愈來愈複雜,Controller 隨之也變得愈來愈臃腫。同時,Controller 也被認爲是 View 的一部分,這樣 Model 與 View 間並無徹底隔離、解耦。 MVP (Model-View-Presenter) 就是在這樣的背景下產生的,其將展現邏輯提取爲一個單獨的層(Presenter),簡化了 Controller,也完全隔離了 Model 與 View。

新產生的 Presenter 層有如下特色:

  • UI 無關 (在 Presenter 中不能包含 UIKit 相關頭文件);
  • 處理展現邏輯;
  • Model 與 View 間的橋接者。

Model View View-Model


最近兩三年對 MVVM(Model View View-Model) 的討論比較多,其提出的願景也是爲了簡化 Controller、完全將 View 與 Model 解耦、並提供 Data Binding。

在 MVVM 中 Controller 被認爲是 View,更準確的說是:

Rules


在 MVVM 模式中,各模塊間的依賴關係、數據流向、數據傳遞的格式都有嚴格的規定:

如上圖所示,View、View Model 以及 Model 須要遵照如下規則:

  • View(UIViewController/UIView): 1.能夠依賴(持有) View、View Model,便可直接調用其方法; 2.不能依賴(持有) Model、Model Object(Item); 3.UI 綁定到View Model上(如:titleLabel.text->viewModel.title) 4.經過 RACCommands/RACActions 或直接調用 View Model 的方法將用戶事件傳遞給 View Model。

  • View Model: 1.能夠依賴(持有) View Model、Model 以及 Model Object; 2.不能依賴(持有) View、Raw Model Object; 3.其公開屬性只能是基礎數據類型(NSInteger、NSString等)或其餘 View Model; 4.將 Model Object 轉換成可直接在 View 上顯示的屬性或Sub View Model(展現邏輯); 5.接受來自 View 或 Sub View Model 的輸入(用戶事件)。

  • Model (Layer): 1.能夠依賴(持有) 其餘 Model、Model Object、Data Source、Raw Model Object; 2.不能依賴(持有) View Model、View; 3.將 Raw Model Object 轉換爲 Model Object; 4.爲 View Model 提供數據(異步)。

其中,View 與 View Model 相似 UIView 與 CALayer 的關係,一一對應(包括層次結構):

Data Binding


從上圖可知,在 MVVM 中數據流方向與依賴關係正好相反,數據流的流動就是創建在 Observer Synchronization 思想基礎之上。

從 Data Source 到 Model、Model 到 View Model 可採用通常的同步方法,如:Delegate、Notification 以及 block 等。而從 ViewModel 到 View 的 Data Binding 是 MVVM 模式與其餘 MV* 模式最大的區別。

遺憾的是 iOS 並無原生的 Data Binding 方式,目前大概只能經過兩種方式實現 Data Binding:KVO 或 ReactiveCocoa。KVO/RAC是一種更加激進的 Observer Synchronization:

  • 優點:綁定關係肯定後,同步更加方便;
  • 劣勢:數據流不直觀,調試較困難;

MVVM VS. MVP


MVVM 與 MVP 有不少類似的點:

  • 將展現邏輯從 Controller 中提取出來(分別放到 View Model 和 Presenter 中);
  • 分別在 View Model、Presenter 中響應用戶事件;
  • 分別經過 View Model、Presenter 鏈接 View 與 Model;
  • 解耦 View 與 Model。

二者最大的區別在於:MVVM 有 Data Binding 而 MVP 沒有。

華山論劍——MV* VS. MVVM


根據是否有 Data Binding,可將常見 GUI 構架分爲兩大陣營:

  • 有 Data Binding:MVVM;
  • 沒有 Data Binding:MVC、MVP、MVCS 等。

MVVM 的 Data Binding 在必定程度上增長了編碼的複雜度、數據流也變得不夠直觀、調試難度也有所增長。但對於數據可變的場景,一旦經過 Data Binding 將 View 與 View Model 綁定起來,在數據變化時,會自動映射到 UI 上,十分方便。

根據展現邏輯是否獨立於 Controller,可分爲:

  • 獨立:MVVM、MVP
  • 不獨立:MVC、MVCS

MVVM、MVP 分別將展現邏輯從 Controller 中提取出來,使 Controller 獲得必定程度的簡化,在展現邏輯複雜的狀況下,效果更加明顯。

沒有好壞,只有適合


經過上述分析,咱們能夠看到,常見幾種架構:MVC、MVCS、MVP、MVVM 並無絕對的好壞之分,只是各有不一樣的適用場景。 咱們在選擇時能夠根據如下兩點做爲參考依據:

  • 數據是否可變(UI是動態仍是靜態) 靜態:MV* 動態:MVVM
  • 展現邏輯是否複雜 複雜:MVVM、MVP

萬變不離其宗——MVC是根


MVCS、MVP、MVVM 等各類新生架構,雖各有不一樣,但都是源自於 MVC,它們的核心思想一直沒變,也不能變:

  • Separated Presentation;
  • Observer Synchronization.

實例


理論的東西講了很多,下面結合實際的項目,看看應該如何選擇架構。

書籍詳情頁


書籍詳情頁在整個 QQ 閱讀 app 中,不管是展現仍是業務邏輯都是最複雜的一個模塊。

  • 在書籍下載過程當中下載按鈕須要顯示下載狀態(進度)——動態 UI;
  • 評分、做者分類、包月相關提示、打賞、粉絲榜等展現邏輯十分複雜。

所以,該模塊採用 MVVM 架構比較合適。遺憾的是,當時設計該模塊時沒有充分意識到其複雜程序,而是選擇了傳統的 MVC 架構。結果形成詳情頁 Controller 十分複雜,下面這段就是 Controller 中根據下載狀態修改 toolbar 上3個按鈕狀態的代碼(ps:看不清不要緊,只要能看出其很複雜便可^_^):

同時,大量的展現邏輯也耦合在了各個 View 中:
若是,採用 MVVM 架構,各類展現邏輯能夠放到相應的 View Model 中,讓 View 只專一於 layout。同時在 View Model 中處理下載相關邏輯,使下載邏輯與 View 解耦。

信息流


信息流做爲 QQ 閱讀一大亮點,能爲用戶個性化推薦書籍,是整個 app 中最重要的一個頁面:

信息流是最重要的頁面,但不是最複雜的頁面:

  • 信息流的數據是靜態的,在顯示過程當中不會改變——靜態UI;
  • 展現邏輯相對簡單。

所以,信息流模塊不必使用複雜的 MVVM 架構。

不少場景屬於此類情形:經過 UITableView 列舉多行靜態數據,若數據有更新時直接 reload tableview。

無劍勝有劍 皆可爲劍


各類分層架構都是前輩充滿智慧的寶貴經驗,值得尊敬、借鑑、學習,但也沒必要拘泥於形式,重點是理解其背後的思想。設計模式有六大原則:

  • 單一職責原則
  • 里氏代換原則
  • 依賴倒轉原則
  • 接口隔離原則
  • 迪米特法則
  • 開放-封閉原則

其中除了里氏代換原則,其餘五大原則都是分層架構的指導思想。只要咱們深入理解並能嚴格遵照這些原則,不管咱們選擇哪一種架構、或在其基礎上進行衍化,都能設計出高質量的代碼。

小結


代碼設計、架構選擇及理解仁者見仁、智者見智,但經典的設計理念是公認的、也是通過時間檢驗的。

參考資料

GUI Architectures

Model-View-Controller

Introduction to MVVM

Lighter View Controllers

ReactiveCocoa and MVVM, an Introduction

On MVVM, and Architecture Questions

相關文章
相關標籤/搜索