第21讲我们从操作系统交互子系统的角度审视了图形界面程序的结构。今天,我们切换视角,站在应用架构的角度,讨论如何设计一个桌面应用程序。
MVC 是桌面开发中流传最广的架构范式,但"一千个人眼中有一千个哈姆雷特"。同样叫 MVC,不同人的理解和实践差异巨大。许式伟指出,判断架构优劣的关键不是套用什么模式,而是理解每一层的职责边界和依赖关系。只有心中有架构法则,才能对 MVC 做出正确的理解与设计。
一种常见的误解是将 MVC 等同于计算机的 Input-Process-Output 模型:Model 是 Input,View 是 Output,Controller 是 Process。这种理解并不准确。
更精确的理解:
View 之所以被理解为 Output,是因为 Model 发出 DataChanged 事件后,View 监听并更新自身——从数据角度看,View 是 Model 的镜像。
在讨论 MVC 各层设计之前,需要明确架构优劣的基本法则:
第二条的后半句常常被忽视——一个层如果只做转发、不做实质处理,同样违背单一职责。
Model 层不是"数据",更不是"数据库表"。Model 层是承载业务逻辑的 DOM(文档对象模型)——面向对象意义上的数据,既有数据结构,也有访问接口。
两种常见架构误区:
| 误区 | 做法 | 问题 |
|---|---|---|
| 误区一 | Controller 直接操作数据库 | Model 层不干事情,违背单一职责 |
| 误区二 | 基于 ORM 实现 Model 层 | Model 接口被技术选型绑架,业务语义丢失 |
正确的 Model 层设计原则:
为什么 DataChanged 事件如此重要?
这与 CPU 的中断机制、操作系统的信号机制一脉相承——用事件回调解决需求的变化点,是架构设计中的普适策略。
View 层有两个职责:
View 层的设计考量:
| 问题 | 说明 | 建议 |
|---|---|---|
| View 不一定生成所有可见元素 | Controller 临时创建的 View 属于 Controller,不属于 MVC 的 View 层 | 区分 View 层 View 与 Controller 辅助 View |
| 委托机制需要灵活 | 一组界面元素的交互事件可能需要共同委托 | 支持组合式委托 |
| View 与 Model 的数据耦合 | View 需要知道数据结构细节来渲染 | Model 提供专享只读访问接口,控制扩散 |
| 局部更新是性能关键 | 全量重绘简单但性能差,局部更新复杂但必须 | 复杂时引入 ViewModel 层 |
ViewModel 层的本质:当 View 的局部更新优化足够复杂时,需要在 Model 和 View 之间引入 ViewModel 层。ViewModel 为 View 量身定制数据组织方式,与 View 呈一一对应关系(双向数据绑定)。
许式伟倾向于将 ViewModel 视为 View 层的内部拆分,而非独立的"MVVM"模式——因为 ViewModel 的存在是因为 View 太复杂,而不是因为架构需要一个新的层。
典型案例:Word 的排版引擎
Word 的 Model 层是数据流式文档,但用户最常用的界面是分页视图。这需要一个 ViewModel 层按分页结构组织数据,排版引擎负责维持 Model 与 ViewModel 的数据一致性。
Controller 层与 Model 层、View 层有一个根本差异:Controller 可以且应该被正交分解。
Controller 正交分解的原则:
分层依赖关系:
Controller层(最上方)
↓ 知道 Model 和 View
View层(中间层)
↓ 持有 Model 的 DOM 指针
Model层(最底层)
↓ 不知道任何上层桌面程序除了支持用户交互,还需提供二次开发接口(API)。最佳 API 提供位置是 ViewModel 层——因为 Model 层虽然也能提供 API,但可能缺少 Selection 等与界面状态相关的信息。
假设开发一个跨平台的图片编辑器。如果将裁剪、滤镜、图层等逻辑都放在 Controller 层,那么每个平台(Windows/macOS/iOS/Android)都需要重新实现这些逻辑。如果将这些逻辑下沉到 Model 层,则只需实现一次,各平台的 Controller 只需调用 Model 接口即可。
Model 层越厚,跨平台成本越低。
某 CRM 系统的 Controller 层直接编写 SQL 语句操作数据库。当需要从 MySQL 迁移到 PostgreSQL 时,所有 Controller 都需要修改。更严重的是,业务逻辑散落在各 Controller 中,无法独立测试。
正确做法:Model 层封装业务逻辑,对外提供"创建客户""查询订单"等业务语义接口,Controller 只调用这些接口。
某电商应用的 View 层根据用户等级计算折扣价格并直接显示。当需要在 API 中也返回折扣价格时,不得不复制计算逻辑。正确做法:折扣计算属于 Model 层,View 层只负责展示 Model 返回的折扣数据。
以 Office 软件为例:
这三个 Controller 完全正交,可以独立开发、独立测试、独立禁用。
交叉引用:本章的 MVC 架构分析是第23讲中浏览器环境下 MVMP 模式的基础,也是第24讲中跨平台架构策略的理论依据。Model 层做厚的设计原则,直接来源于第01-19讲中反复强调的"稳定点与变化点"分析方法。