GUI架构演进之MVC(一)
背景
说到GUI架构,我们一般会想到MVC,MVP,MVVM等;但是关于它们之间的关键区别,网络上的很多解释让人迷惑,看完之后令人更难区分它们了。
如果你也是在看完各种解释之后仍然无法正确区分它们,甚至觉得MVP和MVVM只不过是一个新名词而与MVC无异时,你应该阅读我的这篇文章。
实际上,造成这种困惑的关键原因在于MVC;经过漫长的时间,MVC的原始定义被修改了,甚至被曲解了;而MVP和MVVM是相对于原始MVC来定义的。
例如,有些人就没有正确区分M(Model)和C(Controller),仅将M理解成数据类,而将对数据的操作理解为C,后面可以看到,这是极其错误的;
例如,Cocoa文档所描述的MVC实际上是MVVM,具体可以看微软大牛写的文章Confusion over definition of Controller in MVC和Whats a controller anyway?。
基本定义
问题的关键是追根溯源,探究MVC的原始定义,搞清楚M,V和C的精确定义;SmallTalk的早期文档How to use Model-View-Controller (MVC)对此有描述。
一般来说,GUI程序可以划分为问题建模,视觉显示或反馈和用户输入这三部分,而这也分别对应着M(Model),V(View)和C(Controller)。
Model:应用程序问题域的建模,同时包括数据和行为部分,响应数据获取和更新请求;
View:负责应用程序的界面展示,一般会从Model请求数据;
Controller:负责接收并解释用户输入(例如鼠标,触摸,键盘),驱动Model和View做出反应,一般会请求Model更新数据,请求View刷新;
从这里可以看出,Controller是界面展示的一部分而非是问题建模的一部分,这一点是需要重点注意的。
MVC通信三角
在程序中,Model,View和Controller之间必然要相互通信,具体的通信模式视情况而定,这里只说明最初始的模式。
View和Controller本来就是协同设计的,它们互相持有对方的实例,相互之间按需进行通信(接口调用);
View和Controller都持有Model的实例,并根据需要从Model获取数据或者请求Model更新数据;
Model不能直接持有View和Controller的实例,它主要以两种方式参与到通信过程中来:
作为被动Model(Passive Model),不主动参与通信过程,而是由View和Controller来主动执行查询或者更新,View的刷新时机由Controller控制,适用于较简单的情况;
作为主动Model(Active Model),采用观察者模式,Model主动将变化通知给View或者Controller,View的刷新时机由Model控制,适用于比较通用的情况,即主流情况;
MVC组成MVC三角,通常一个程序中存在很多MVC三角,它们之间相互关联和协作,共同完成程序提供的功能。
SmallTalk的MVC实现
上面关于MVC的描述来自于早期SmallTalk的GUI设计,因此了解下SmallTalk的MVC实现是有意义的。
SmallTalk中内置了对MVC编程范式的支持,存在View,Controller以及Model类,分别对应着这几种角色,对编程起到规范化作用。
在SmallTalk中,View和Controller一般是形式化的,存在丰富的类继承层次供应用程序直接使用,或者继承修改再使用;Model则属于应用程序问题域,一般不能形式化,由应用程序自由发挥,但是SmallTalk为Model与View和Controller之间的通信提供了机制和规范:
Model发送changed消息给它的观察者View,触发View的update方法被调用,View通过一种反射的方式(aspect)调用Model来获取数据,然后刷新自己;这种反射的技术避免了View与Model的强依赖,否则将导致过多的类继承来处理从各种Model中获取数据的逻辑;这种技术也可以理解为是一种Adapter,一种ValueConverter,甚至是DataBinding。
大体过程如下:
1.View组织成树形结构,View只负责展示,不负责与用户交互;
2.每个View对应一个Controller,Controller负责处理用户交互;
3.Controller也组织成树形结构,与View的树形结构相对应;
4.存在顶层或全局Controller,负责总体协调和提供系统行为;
5.整个GUI由顶层Controller启动control loop进入事件循环,接收用户交互;
6.当用户与程序交互时,焦点View对应的Controller被选择来处理用户交互;
7.Controller在处理用户交互时,除了可能会更新Model,也会直接或间接刷新View;
可以看出,SmallTalk的GUI与Android的GUI大体相同,区别在于SmallTalk严格区分了View和Controller,而Android没有进行这种区分;SmallTalk的这种严格区分View和Controller的做法,对我们今天来理解MVC造成了比较大的困惑;事实上,随着MVC的演进,这种严格区分View和Controller的做法被逐渐淡化了,将View和Controller合二为一为Widget则成为主流(例如Android),但应用程序仍需要保留一个顶层Controller来负责组装串联各部分的逻辑(例如Android的Activity);但是这种Widget的形式淡化了Controller,对编程缺乏引导和规范,导致很多时候写出的代码并非遵循MVC模式,而是Forms and Controls模式,这个后面再写文章单独说明。