MVC MVP MVVM Redux 架构介绍
从我开始学习编程开始就一直比较迷惑 MVC 到底是什么?一直没有找到明确的、权威的定义。各种书籍,文章描述的虽然大体相似,但是也有差异,甚至是显著的差异。MVC 已经拥有将近50年的历史了,讨论 MVC 的文章大都都会追溯到 Smalltalk 实现了 MVC 架构,但是 Smalltalk 对大多数人来说只是传说,没几个人对 Smalltalk 是咋回事,所以就让理解 MVC 更加扑所迷离了。
这里我尝试着来把 MVC 涉及的内容整理梳理一下。大多数系统最终都是给人使用的,那么就必然需要给用户呈现一个交互界面。为了实现交互界面,我们需要有数据,需要UI界面把数据做有效的展示,还需要有用来触发用户行为的界面元素,比如按钮、输入框、表格等等,另外还需要响应用户操作的处理逻辑,比如用户点击按钮,需要怎么处理,界面需要做怎样的变化,数据需要做怎样的变化。
刚开始学编程的时候,大家都习惯把数据、界面元素、用户操作响应逻辑都写在一起。这样就稍微复杂一点界面,都会导致代码写得如一团乱麻。慢慢的开始有人提出应该将显示逻辑、数据逻辑、还有用户操作响应处理这些不同的逻辑分开,也就是现在我们经常听到的关注点分离、单一职责这样的设计原则。最终出现了 MVC 这样的架构:
M 表示 Model 也就是数据模型
V 表示 View 也就是界面视图
C 表示 Controller 也就是控制,用来协调用户操作、Model、和 View。
但是 MVC 只是给出了这样一个大致的指导原则,并没有具体指明 MVC 各个部分应该承担具体什么职责,相互之间应该如何交互。这些很大程度上都是由开发工具或者开发框架定义的,如果工具或者框架没有定义,那么就完全靠开发人员自由发挥了。随着 Web 应用、移动端应用的流行,各种开发框架层出不穷,MVC 衍生出了几种变体,比如 MVP 和 MVVM。
1.MVC 框架分类
从大的层面可以将 MVC 框架分为服务端参与的 MVC 框架 和 纯客户端 MVC 框架。
首先,服务端 MVC 框架,比较典型的例子有 Spring MVC 框架和 微软的 ASP.NET MVC 框架。这类 MVC 框架中的 Model 通常是领域实体模型,数据通常会存储在数据库中。View 则通常是模板语言编写的页面模板,比如 Jsp、Freemarker、Asp等,模板与 Model 绑定,最终渲染成 Web 端的 html 页面。而 Controller 则处理 Web 前端请求,协调调用服务层业务逻辑,获得结果,并渲染成合适的前端结果返回。
第二大类就是纯客户端的 MVC 框架了。当数据已经到达客户端后,又出现了 MVC 需求,需要管理客户端的数据模型、需要 View 将数据展示给用户并给用户操作触点、需要协调处理用户的操作。下面我们将详细的描述一下客户端的 MVC 框架。
1.1 客户端 MVC 框架
1.1.1 MVP
MVP 中的 MV 和 MVC 中 MV 对应,P和C对应,概念上也相近,只是更加明确的定义了各个组件的职责。根据 Martin Fowler 的文章 MVP 又分为Passive View和Supervising Controller。
Passive View(被动视图模式)
顾名思义,被动视图模式中,View 是完全被动的,只有显示数据逻辑和触发操作入口,所有的逻辑都由 Presenter 承担。View 和 Model 不直接交互,Presenter负责将数据通过 View 接口设置到视图控件上,负责响应 View 的事件,做出处理,根据需要更新 Model,然后触发视图重新加载或者刷新。
Supervising Controller(监督控制器)
在 Passive View 中,Presenter 需要通过 View 接口来与之交互,所以 View 需要提供非常多的接口,让客户端开发变得有一些繁琐。Supervising Controller 模式通过数据绑定(比如AngularJS、Vue的双向绑定)为 View 与 Model 建立映射关系来消除这种繁琐,而且开发框架通常都会提供声明的方式建立绑定关系。视图发生变化时(比如,用户在输入框中输入),绑定的 Model 自动跟着变化,而 Model 数据发生变化的时候,视图也自动更新变化的内容。除了这种简单的映射,复杂的逻辑仍然需要 Supervising Controller 来处理。
我们知道,通常客户端程序是很难做自动化测试的,由于 Passive View 和 Supervising Controller 模式都将复杂的逻辑放在了 Presenter 中,并且通过View 接口来与 View 进行交互,所以我们可以将测试焦点放在 Presenter 上(用 Mock 实现 View 接口)。也即是说MVC 技术让我们的客户端程序可测试性大大提升。这是一个非常重要的特性。
1.1.2 MVVM(Model-View-View Model)
MVVM 也叫Presentation Model。光看图是与标准的 MVP 没啥不同,区别主要还是在职责上。Model 的职责保持不变,View 的职责被分成了两部分,一个是可视化显示,展示数据和用户操作交互(按钮等),这部分职责是 View 的天然职责,仍然由 View 来承担;另外一部分是 View 的状态,也就是 View 中动态的部分,比如数据(输入框中的内容,下拉框选中的值等等),还包括控件的 enable 状态,表格中哪一行是选中状态等,这部分职责转移到了 View Model(Presentation Model)中了。所以 View 不再需要与 Model 做绑定,而是与 View Model 绑定,View 与 Model 没有直接交互。View Model 除了需要响应用户操作外,还需要维护视图状态,在 MVP 中,Presenter 也是需要维护视图状态的,但是Presenter 是将视图状态设置到视图上,Presenter自己并不持有这些状态。而 MVVM 中,View Model 需要是视图状态的权威来源,视图只是反映 View Model 的状态。
与 MVP 一样,View 是负责显示,没有逻辑,所以也带来了可测试性的提高。至此,传统的 MVC 相关的内容就介绍完了,下面介绍的是与这些 MVC/MVP/MVVM 三角恋完全不同,是非常具有创新性的选手 React Redux,如果不想了解,可以不用继续阅读,并不影响你的 MVC 的理解。但是下面的内容是值得花点时间了解一下的。
1.1.3 React Redux
以上所有都是 MVC 的变体,React Redux 则。React Redux 是个 Web 前端开发框架(Redux 也可以搭配 React Native 开发跨平台的移动端应用),涉及的内容很多,这里不展开,只简单介绍下几个原则和与显示相关的几个重要组成部分及职责。
Redux 强调了以下几个原则。
首先,Redux 框架对数据流做了一个重要的限制,也即所谓的“单向数据流”。限制意味着不自由,但是不自由不一定都是坏事,MVC 中的双向甚至是多向数据流,带来了数据状态变化不可预测,变得更复杂。
其次,React 强调纯函数。满足以下两个条件的函数就是纯函数:
给出同样的参数值,该函数总是求出同样的结果。比如 Javascript 函数不能依赖外部变量,如果依赖外部变量,下次调用的时候,如果外部变量值发生了变化,那么函数的结果很可能就会发生变化。
结果的求值不会促使任何可语义上可观察的副作用或输出,例如易变对象的变化。
最后,全局唯一的 Store 对象。大体可以认为是 MVC 中的 Model。
Redux 几个主要组成部分:
> View :与 MVC 中的 View 类似,负责显示逻辑。同时响应用户的操作,生成 Action,通过 Dispatcher 派发出去。
> Actions:用户通过视图触发的操作被封装成为 Action。Action 会经由 Dispatcher 派发给 Store。
> Dispatcher:Action 派发器,由框架提供,直接使用。
Store:客户端内存中的对象,通常是个树状结构,一个 View 中的一个组件通常与 Store 中的某个节点对应,展示其内容,并且在 Store 发生变化的时候,Redux 框架会高效的只更新变化数据对应的 View 的组件。Store 更新只能通过触发 Action 来实现,所以状态更新是可预测的,并且是比较容易预测的。
Reducer:看名字,与 map-reduce 有点像,是个归纳器,不过这里和 map-reduce 没什么关系,是响应处理 Action 的地方,也可以说是处理页面相关业务逻辑的地方。Reducer 响应 Action,执行逻辑,返回新的 state,框架将会将 state 更新到 Store,而这又会触发框架计算受影响的 View 组件,并更新。大致可以与 MVC 中 C 对应吧。不过由于 Redux 与 MVC 是完全不同的理念,这样强行的做概念映射是没有意义的。只是大致帮助理解。
Redux 带来了很多好处(当然从上面的描述是看不出来的,需要深入的学习才能体会到):
> 可测试性,Redux 强调纯函数,同时框架本身考虑到了与主流前端测试框架比较 Mock、Jest 的结合。
调试变得更加容易
> Undo/Redo 将会变得非常容易实现,通过回放 Action,就可以重复 Store 的变化。
> 性能更加可控,数据更新只刷新受影响的控件对应的部分
> 大型前端应用开发效率更高
经过几十年的发展,MVC 也持续迭代更新,出现了 MVP、MVVM、以及具有创新性的 Redux。作为开发人员,一直对这些概念似懂非懂,终于有时间做了一次深入的梳理,希望对你有帮助。