MVC、MVP、MVVM
看了几篇 MVC 的 MVVM 的文章,感觉一些背景和区分都不太清楚,一部分还主要是根据一些实际的开发反过来硬套 MVC。不过最后根据其中一些大佬们整理的文章,也总算有一点点头绪,这里记录一下
在架构设计中,我们面对的模式主要有:
- MVC
- MVP
- MVVM
这三种模式都是把所有的实体归类到了下面三种分类中的一种:
- Models(模型)- 数据层,或者负责处理数据的数据接口层。
- Views(视图) - 展示层 (GUI)
- Controller/Presenter/ViewModel(控制器/展示器/视图模型) - 它是 Model 和 View 之间的胶水或者说是中间人。一般来说,当用户对 View 有操作时它负责去修改相应 Model;当 Model 的值发生变化时它负责去更新对应 View。
通过以上的分类:
- 更容易理解
- 方便重用
- 可以独立进行测试
MVC 架构
由来
在 1979 年,经典 MVC 模式被提出。
当时,人们一直试图将纯粹描述思维中的对象与跟计算机环境打交道的代码隔离开来,而 Trygve Reenskaug 在跟一些人的讨论中,逐渐剥离出一系列的概念,最初是 Thing、Model、View、Editor。后来经过讨论定为 Model、View 和 Controller。作者自言“最难搞的就是给这些架构组件起名字”。
因为当时的软件环境跟现在有很大不同,所以经典 MVC 中的概念很难被现在的工程师理解。比如经典 MVC 中说:“view 永远不应该知道用户输入,比如鼠标操作和按键。”对一个现代的软件工程师来说,这听上去相当不可思议:难道监听事件不需要类似这样的代码吗?
view.onclick = ……
但是想想在 70 年代末,80 年代初,我们并没有操作系统和消息循环,甚至鼠标的光标都需要我们的 UI 系统来自行绘制,所以我们面对的应该是类似下面的局面:
mouse.onclick = ……
mouse.onmove = ……
当鼠标点击事件发生后,我们需要通过 view 的信息将点击事件派发到正确的 view 来处理。假如我们面对的是鼠标、键盘驱动这样的底层环境,我们就需要一定的机制和系统来统一处理用户输入并且分配给正确的 view 或者 model 来处理。这样也就不难理解为什么经典 MVC 中称”controller 是用户和系统之间的链接”。
因为现在的多数环境和 UI 系统设计思路已经跟 1979 年完全不同,所以现代一些喜好生搬硬套的“MVC”实现者常常会认为 controller 的输入来自 view,以至于画出 model、view、controller 之间很奇葩的依赖关系:
我们来看看 Trygve Reenskaug 自己画的图:
值得注意的是,其实 MVC 的论文中,还提到了”editor”这个概念。因为没有出现在标题中,所以 editor 声名不著。MVC 论文中推荐 controller 想要根据输入修改 view 时,从 view 中获取一个叫做 editor 的临时对象,它也是一种特殊的 controller,它会完成对 view 和 view 相关的 model 的修改操作。
控件系统
MVC 是一种非常有价值的架构思路,然而时代在变迁,随着以 windows 系为代表的 WIMP(window、icon、menu、pointer)风格的应用逐渐成为主流,人们发现,view 和 controller 某些部件之间的局部性实际上强于 controller 内部的局部性。于是一种叫做控件(control)的预制组件开始出现了。
控件本身带有一定的交互功能,从 MVC 的视角来看,它既包含 view,又包含 controller,并且它通过“属性”,来把用户输入暴露给 model。
controller 的输入分配功能,则被操作系统提供的各种机制取代:
- 指针系统:少数 DOS 时代过来的程序员应该记得,20 年前的程序中的“鼠标箭头”实际上是由各个应用自己绘制的,以 MVC 的视角来看,这应当属于一个”PointerView”的职责范畴。但是 20 世纪以后,这样的工作基本由操作系统的底层 UI 系统来实现了。
- 文本系统:今天我们几乎不需要再去关心文本编辑、选中、拖拽等逻辑,对 web 程序员可以尝试自己用 canvas 写一个文本编辑框来体验一下上个时代程序员编写程序的感受。你会发现,选中、插入/覆盖模式切换、换行、退格、双击、拖拽等逻辑异常复杂,经典 MVC 模式中通常使用 TextView 和 TextEditor 配合来完成这样的工作,但是今天几乎找不到需要我们自己处理这些逻辑的场景。
- 焦点系统:焦点系统通过响应鼠标、tab 键等消息来使得控件获得操作系统级唯一的焦点状态,所有的键盘事件通常仅仅会由拥有焦点的控件来响应。在没有焦点系统的时代,操作系统通常是单任务的,但是即使是单一应用,仍然要自己管理多个 controller 之间的优先权和覆盖逻辑,焦点系统不但从技术上,也从交互设计的角度规范化了 UI 的输入响应,而最妙的是,焦点系统是对视觉障碍人士友好的,现在颇多盲人用读屏软件都是强依赖焦点系统的。
所以时至今日,MVC,尤其是其中 controller 的功能已经意义不大,若是在控件系统中,再令所有用户输入流经一个 controller 则可谓不伦不类、本末倒置。MVVM 的提出者,微软架构师 John Gossman 曾言:“我倾向于认为它 (指 controller) 只是隐藏到后台了,它仍然存在,但是我们不需要像是 1979 年那样考虑那么多事情了”
MVP
由来
1996 年,Taligent 公司的 CTO,Mike Potel 在一篇论文中提出 Model-View-Presenter 的概念。
在这个时期,主流的 view 的概念跟经典 MVC 中的那个“永远不应该知道用户输入”的 view 有了很大的差别,它通常指本文中所述的控件,此时在 Mike 眼中,输入已经是由 view 获得的了:
Model-View-Presenter 是在 MVC 的基础上,进一步规定了 Controller 中的一些概念而成的:
对,所以,不论你按照 Mike 还是 Trygve 的理解方式,MVP 和 MVC 的依赖关系图应该是一模一样的!因为 Mike 的论文里说了“we refer to this kind(指应用程序全局且使用 interactor, command 以及 selection 概念的)of controller as a presenter”。presenter 它就是一种 controller 啊!
标记语言和 MVVM
随着 20 世纪初 web 的崛起,HTML 跟 JS 这样标记语言 + 程序语言的组合模式开始变得令人注目。逐渐推出的 Flex、Sliverlight、QT、WPF、JSF、Cocoa 等 UI 系统不约而同地选择了标记语言来描述界面。
在这样的架构中,view(或者说叫控件),不但是从依赖关系上跟程序的其他部件解耦,而且从语言上跟其它部分隔离开来。
标记语言的好处是,它可以由非专业的程序员产生,通过工具或者经过简单培训,一些设计师可以直接产生用标记语言描述的 UI。想要突破这个限制使得 view 跟其它部分异常耦合可能性更低。
然而这样的系统架构中,MVC 和 MVP 模式已经不能很好地适用了。微软架构师 John Gossman 在 WPF 的 XAML 模式推出的同时,提出了 MVVM 的概念。
WPF 得 MVVM 正式说明了它的 view 的概念跟 MVC 中的 view 的概念的区别。
在 MVVM 模式中,数据绑定是最重要的概念,在 MVC 和 MVP 中的 view 和 model 的互相通讯,被以双向绑定的方式替代,这进一步把逻辑代码变成了声明模式。