MVC 对比 MVVM 架构
一句话总结
- MVC:Controller 手动”指挥” View 和 Model,View 的更新靠代码一行行写。
- MVVM:View 和 ViewModel 通过数据绑定自动同步,几乎不需要手写”更新界面”的代码。
两种架构各是什么
MVC(Model-View-Controller)
| 角色 | 职责 |
|---|---|
| Model | 数据和业务逻辑 |
| View | 显示界面 |
| Controller | 接收用户操作,修改 Model,并通知 View 刷新 |
数据流:用户点击 → Controller 处理 → 改 Model → Controller(或 Model 的信号)手动调用 View 更新。
MVVM(Model-View-ViewModel)
| 角色 | 职责 |
|---|---|
| Model | 数据和业务逻辑 |
| View | 只负责声明”界面长什么样”,并绑定到 ViewModel 的属性 |
| ViewModel | 把 Model 的数据加工成”界面直接能用的状态”,并暴露操作命令 |
数据流:ViewModel 的属性一变,绑定机制自动刷新 View;用户操作调用 ViewModel 的方法。ViewModel 完全不知道 View 的存在。
关键区别
| 要解决的问题 | MVC 风格 | MVVM 风格 |
|---|---|---|
| 原始计数存在哪 | Model | Model |
| 用户点击交给谁 | Controller | ViewModel 的操作方法 |
| View 怎么更新 | Controller 手动调用,如 label->setText() |
属性绑定自动更新 |
| 中间层是否认识 View | Controller 通常持有 View 的指针 | ViewModel 不引用 View |
| View 里有多少逻辑 | 常有少量逻辑(事件处理) | 几乎没有,只做声明 |
| 可测试性 | Controller 依赖 View,较难单测 | ViewModel 是纯逻辑,易单测 |
| 样板代码 | 多(大量 connect 和 set) | 少 |
样板代码(boilerplate code)
样板代码(boilerplate code)指的是:为了让功能跑起来,必须反复写、但本身没有”业务含义”的重复代码。它不是你真正想实现的逻辑,只是”把东西连起来”的体力活。
MVC 里为什么多:
QWidget + MVC 中,界面上每个会变化的东西,都要你手动写两类代码:
connect:把控件的信号、Model 的信号和 Controller 的槽一根根连起来set:数据变了之后,手动调用setText()、setEnabled()、setVisible()等去刷新控件
比如一个登录页,有”状态文字、按钮是否可用、进度条是否显示”三处要随数据变化:
1 | // 每多一个要变化的界面元素,就多几行 connect + set |
界面越复杂,这类代码越多,而且它们都是同一种模式的重复,这就是”样板”。
MVVM 里为什么少:
QML 里一句绑定就代替了上面的 connect + set:
1 | Label { text: vm.status } // 代替 connect + setText |
“数据变了就更新界面”这件事由 QML 的绑定机制自动完成,你不用再写。
Qwidget + MVC 对比 QML + MVVM
QWidget + MVC 的典型写法
1 | // Controller:持有 View 和 Model,负责"粘合" |
特点:每个界面变化都要手动 setText / setEnabled / setVisible,Controller 必须认识具体的控件。
QML + MVVM 的典型写法
1 | // ViewModel:纯 C++,通过 Q_PROPERTY 暴露状态 |
1 | // View:只做声明式绑定,没有"更新界面"的代码 |
特点:ViewModel 里只管改属性、发 NOTIFY 信号,界面自己会跟着变,ViewModel 完全不碰任何 UI 控件。
为什么 QWidget 通常搭配 MVC?
核心原因:QWidget 是命令式(imperative)UI,没有原生的数据绑定。
- 界面是”对象 + 方法调用” :按钮、标签都是 C++ 对象,要改显示就得调用
setText()等方法,天然需要”某个东西”来负责调用,这个角色自然就是 Controller。 - 没有绑定机制:想让 View 跟 Model 同步,只能靠
connect信号槽 + 手写刷新代码,这正是 MVC 的 Controller 在做的事。 - 控件本身就带逻辑:QWidget 的事件处理(
mousePressEvent等)和 View 绑得很紧,View 和 Controller 很难彻底分开。 - Qt 自带的 Model/View 框架本身就是 MVC 的变体(
QAbstractItemModel+QListView/QTableView+QStyledItemDelegate,Controller 的职责被合并进 View/Delegate),所以 QWidget 开发者的思维习惯自然偏向 MVC。
为什么 QML 通常搭配 MVVM?
核心原因:QML 是声明式(declarative)UI,原生支持属性绑定。
- 绑定是语言级特性:
text: vm.status这一句就建立了自动同步,不需要 Controller 去”推”数据,MVC 里 Controller 最重的工作被语言本身接管了。 - Q_PROPERTY + NOTIFY 信号就是为绑定设计的:C++ 对象一暴露属性,QML 立刻能绑定,这个 C++ 对象就是现成的 ViewModel。
- QML 不适合写复杂逻辑:JS 里堆业务逻辑难维护、难测试,所以把逻辑放进 C++ ViewModel,QML 只留声明,职责划分非常清晰。
- C++ 与 QML 天然解耦:ViewModel 不持有任何 QML 对象指针,可以脱离界面单独测试,也可以换一套 QML 界面复用同一个 ViewModel。
一个形象的比喻
- MVC:像餐厅里有个服务员(Controller),客人点菜后,服务员跑去厨房(Model)传话,菜好了再亲自端到桌上(更新 View)。
- MVVM:像自助点餐屏 + 出餐口显示屏,屏幕(View)直接绑定订单状态(ViewModel),状态一变,屏幕自动刷新,不需要人去通知。
补充说明
这是”惯例“而不是”规定“:
- QWidget 也可以做 MVVM,但要自己写绑定(用信号槽模拟,或借助第三方库),收益不大,反而增加复杂度。
- QML 也可以用 MVC,但等于没有利用绑定,是在浪费 QML 的优势。
- 实际项目中,两者的边界也常常混合,比如 QML 里用
QAbstractListModel做列表数据,再配合 ViewModel 管理页面状态。
选择依据一句话:UI 框架有没有声明式绑定,决定了”中间层”是需要手动指挥(Controller),还是只需要暴露状态(ViewModel)。
补充:Qt 的 Model/View/Delegate 与 MVC 有什么区别
前面的 MVC、MVVM 用来讨论界面与业务代码如何组织。
Qt Widgets 的 Model/View/Delegate 主要用于列表、表格、树形控件的数据展示与编辑:
- Model:提供数据访问接口。
- View:管理列表、表格或树的整体展示,以及选中、滚动等交互。
- Delegate:负责单个数据项怎样绘制,以及编辑时使用什么编辑器。
Qt 将传统 MVC 的 View 与部分 Controller 职责合到 View 中,再通过 Delegate 定制数据项的显示和编辑。
例如表格里有一个“年龄”单元格:
| 角色 | 做什么 |
|---|---|
| Model | 提供年龄 18,接受修改后的年龄 |
| View | 确定这一格的位置,处理选中和开始编辑 |
| Delegate | 平时绘制 18,编辑时提供数字输入框,提交新值 |
Delegate 不是改名后的 Controller。 它专门处理数据项的绘制和编辑,不负责整个应用的登录、设备连接等业务流程。
常用的 QStyledItemDelegate 提供这些定制入口:
| 方法 | 作用 |
|---|---|
paint() |
绘制数据项 |
sizeHint() |
提供建议尺寸 |
createEditor() |
创建编辑控件 |
setEditorData() |
将 Model 的数据填入编辑控件 |
setModelData() |
将编辑结果写回 Model |

