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

相关文章
相关标签/搜索