【软考架构】案例分析-MVC、MVP和MVVM分层架构模式
这三种模式共同的目标都是分离关注点,尤其是将用户界面(UI)逻辑与业务逻辑和数据分离,以提高代码的可维护性、可测试性和可扩展性。核心思想: 用户请求直接到达控制器,由控制器协调模型和视图。组件职责:数据流向(基于图13-2):关键特点:应用场景:核心思想: 用户输入先传递给视图,视图再将所有交互委托给Presenter。Presenter是控制中心,完全解耦了视图和模型。组件职责:数据流向(基于图



这三种模式共同的目标都是分离关注点,尤其是将用户界面(UI)逻辑与业务逻辑和数据分离,以提高代码的可维护性、可测试性和可扩展性。
1. MVC(Model-View-Controller)
核心思想: 用户请求直接到达控制器,由控制器协调模型和视图。
组件职责:
- 模型: 代表应用程序状态和核心业务逻辑。它独立于UI,可以响应状态查询,处理业务流程,并通知视图状态已更新。
- 视图: 负责呈现模型的数据给用户。它显示模型状态,并将用户输入数据传给控制器(而非直接与模型交互)。
- 控制器: 作为中间人。它接受用户请求,调用模型来完成业务处理,然后选择视图来显示响应结果。
数据流向(基于图13-2):
- 用户与视图交互(如点击按钮),产生用户请求。
- 请求被发送到控制器。
- 控制器调用模型进行业务处理。
- 模型处理完成后,通知视图有数据更新。
- 视图接收到通知后,向模型进行状态查询,获取最新数据并更新显示。
关键特点:
- 视图和模型之间存在直接通信(视图监听模型的更新通知)。这在现代应用开发中有时会导致视图和模型耦合过紧。
应用场景:
- 传统的Web应用框架(如Spring MVC, ASP.NET MVC)。
- 桌面应用程序。
- 适用于中等复杂度的项目,其中视图与模型的依赖关系相对直接。
2. MVP(Model-View-Presenter)
核心思想: 用户输入先传递给视图,视图再将所有交互委托给Presenter。Presenter是控制中心,完全解耦了视图和模型。
组件职责:
- 模型: 与MVC中的模型职责相同,代表数据和业务逻辑。
- 视图: 变得非常“被动”。它只负责渲染UI和接收用户输入,然后将所有输入转发给Presenter。在图中,它甚至作为“改变的存储”,意味着它可能持有Presenter需要的数据。
- Presenter: 是中间人和控制者。它从视图接收用户动作,更新模型,然后从模型获取更新后的数据,最后主动更新视图。
数据流向(基于图13-3):
- 用户在视图上执行操作。
- 视图将动作通知Presenter。
- Presenter更新模型。
- Presenter从模型获取更新后的数据。
- 模型将变化通知Presenter(或其他中间件,但最终由Presenter处理)。
- Presenter负责更新视图,将渲染好的输出提供给视图显示。
关键特点:
- 视图和模型完全隔离,它们彼此不知道对方的存在,所有通信都通过Presenter。
- Presenter变得很“重”,因为它包含了大量的表现逻辑。
- 由于视图通过接口与Presenter交互,使得单元测试非常方便(可以轻松模拟视图)。
应用场景:
- 对可测试性要求极高的应用程序(如大型企业级应用)。
- Windows Forms、GWT等客户端应用。
- 当需要将UI逻辑与UI组件本身彻底分离时。
3. MVVM(Model-View-ViewModel)
核心思想: 通过数据绑定和命令机制,实现视图和ViewModel的自动同步。ViewModel是视图的抽象,它暴露了视图需要的数据和命令。
组件职责:
- 模型: 与MVC、MVP中的模型职责相同。
- 视图: 负责UI结构和外观。它通过数据绑定声明性地绑定到ViewModel的属性上。当用户操作时,会触发ViewModel的命令。
- ViewModel: 可以看作是一个“视图的模型”。它包含了视图的状态(数据)和行为(命令)。它从模型获取数据,并转换为视图可以直接显示的形式。当模型更新时,ViewModel通过某种机制(如观察者模式)更新自己的状态,从而通过数据绑定自动更新视图。
数据流向(基于图13-4及常见理解):
- 视图通过数据绑定自动显示ViewModel中的数据。
- 用户与视图交互(如点击),触发绑定在ViewModel上的命令。
- ViewModel执行命令,可能会调用模型进行业务处理。
- 模型更新后,通过通知机制(如事件、Observable)告知ViewModel。
- ViewModel更新自身的数据属性。
- 由于数据绑定,视图自动更新以反映ViewModel的最新状态。
关键特点:
- 双向数据绑定是核心,它极大地减少了“胶水代码”。
- ViewModel不知道视图的存在,实现了更彻底的解耦。
- 视图的主动控制器(如Controller/Presenter)被数据绑定框架取代,开发者的工作重心从控制UI元素转向声明数据关系和命令。
应用场景:
- 支持数据绑定的现代UI框架,如 WPF、Silverlight、Angular、Vue.js、Knockout.js。
- 需要快速开发、UI交互复杂且需要保持高度同步的应用。
- 前端开发的首选模式。
总结与对比
| 特性 | MVC | MVP | MVVM |
|---|---|---|---|
| 控制器角色 | Controller | Presenter | ViewModel (通过绑定) |
| 视图状态 | 被动/主动 | 完全被动 | 声明式、被动 |
| 模型-视图关系 | 直接通信(观察者) | 完全隔离 | 通过ViewModel间接连接 |
| 数据流向 | 多向 | 以Presenter为中心的严格单向 | 双向数据绑定(自动化) |
| 可测试性 | 中等 | 高(视图可模拟) | 高(ViewModel独立于View) |
| 耦合度 | 相对较高(View依赖Model) | 低(View和Model解耦) | 极低(View和ViewModel解耦) |
| 典型框架 | Spring MVC, ASP.NET MVC | GWT, Windows Forms | WPF, Angular, Vue.js |
如何选择:
- 选择MVC: 当你的项目相对简单,或者使用的框架是传统的服务端渲染Web框架时。它结构清晰,易于理解。
- 选择MVP: 当你需要极高的可测试性,并且你的开发环境不支持强大的数据绑定(例如在纯JavaScript或Android开发中追求清晰架构时)。
- 选择MVVM: 当你的开发平台原生支持数据绑定(如WPF、Angular、Vue),并且你希望最大化减少手动更新UI的代码,提高开发效率时。这是现代前端和富客户端应用的主流选择。
三种模式,与前后端分离的关系
MVC、MVP、MVVM是前端或表现层内部的设计模式,而前后端分离是一种更宏观的系统架构。
它们之间的关系是:在前后端分离的架构下,前端部分可以选择采用MVC、MVP或MVVM等模式来组织自己的代码结构。
下面我们来详细拆解这种关系。
前后端分离架构的简要回顾
在前后端分离的架构中:
- 后端(Server): 只负责数据和服务。它提供一套标准的API接口(通常是RESTful API或GraphQL),处理业务逻辑、数据库操作、认证授权等。它不关心数据如何呈现给用户。
- 前端(Client): 负责视图和交互。它从后端获取数据(通过调用API),然后根据这些数据渲染出用户界面,并处理所有的用户交互。它不关心数据是如何从数据库查询出来的。
在这种架构下,后端就是一个纯粹的“数据模型”和“业务逻辑”提供方。
三种模式在前后端分离中的角色
我们可以将后端提供的API服务视为一个远程的、共享的Model。那么,前端应用自身的架构,就围绕着如何与这个“远程Model”交互并管理自身的UI状态来展开。
1. MVC 与 前后端分离
- 传统服务端MVC: 在古老的JSP、PHP时代,MVC是发生在服务器端的。控制器接收请求,调用模型,然后选择一个服务端的视图模板(如JSP) 来渲染HTML,最后将完整的HTML页面发送给浏览器。视图和控制器都在后端。
- 前后端分离下的“MVC”: 此时,MVC的架构被“切开”了。
- Model(模型): 位于后端,通过API暴露。
- View(视图): 位于前端,由HTML/CSS/JavaScript(如React/Vue组件)构成。
- Controller(控制器): 角色发生了分裂和演变。
- 一部分控制逻辑转移到了前端,例如:前端路由(React Router, Vue Router)可以看作是一个控制器,它根据URL决定显示哪个视图组件;组件内处理点击事件的方法也是控制器逻辑。
- 后端的API接口本身,也扮演了一部分控制器的角色,它接收前端的请求,决定调用哪个业务模型。
结论: 在前后端分离中,传统的单体MVC架构不再适用,而是演化成了一种跨前后端的、分布式的协作模式。前端内部可能仍然采用类似MVC的思想来组织代码,但整个系统的M、V、C边界已经变得模糊。
2. MVP 与 前后端分离
MVP模式在前端开发中本身就不太流行,但在前后端分离架构下,它的映射非常清晰:
- Model(模型): 就是后端提供的API服务。前端通过HTTP客户端(如Axios)去调用它。
- View(视图): 前端的UI组件。这些组件应该是被动的,只负责展示和收集用户输入。
- Presenter(呈现器): 这是前端的核心逻辑层。它包含:
- 调用后端API(Model)获取或更新数据。
- 处理复杂的UI逻辑和业务规则验证。
- 将获取到的数据转换成视图容易显示的形式。
- 命令视图更新。
在这种模式下,Presenter承担了绝大部分的职责,使得View非常薄,且与后端Model完全隔离,可测试性极高。
应用场景: 在不太依赖数据绑定框架的移动端原生开发(如Android)或一些追求极致测试性的Web项目中,MVP是一个很好的选择。
3. MVVM 与 前后端分离
MVVM是前后端分离架构下,现代前端框架的“灵魂”所在。 它完美地契合了前端应用的开发需求。
- Model(模型): 同样是后端提供的API服务。
- View(视图): 由模板(如Vue的template,Angular的HTML)构成,是声明式的UI。
- ViewModel(视图模型): 这是框架(如Vue.js、Angular)提供的核心能力。
- 它是一个绑定器,通过数据绑定将View和Model连接起来。
- 开发者需要做的就是:在ViewModel中定义状态(数据) 和 方法。
- 当用户操作View(如输入框输入),通过双向数据绑定自动更新ViewModel的状态。
- 当ViewModel的状态变化(可能是通过方法调用后端API后更新了数据),通过数据绑定自动驱动View更新。
关系:
- ViewModel通过服务层(如一个专门的API调用模块) 去与后端Model(API)交互。
- 获取到数据后,更新ViewModel自身的响应式数据属性。
- View由于数据绑定,自动更新。
应用场景: 这正是 Vue.js、Angular 等框架的典型工作模式。它们极大地提升了开发效率,因为开发者无需再写大量的手动DOM操作代码。
总结对比
| 模式 | 在前后端分离架构中的角色 | 典型技术 |
|---|---|---|
| MVC | 架构被“切开”,Model在后端,View和部分Controller在前端。是一种宏观的、跨端的思想。 | 任何前端框架+后端API。React(早期)更接近这种思想。 |
| MVP | 清晰的职责划分。前端内部模式,Presenter作为核心,协调前端View和后端Model。 | 安卓原生开发、GWT、ExtJS等。 |
| MVVM | 现代前端框架的核心模式。通过数据绑定连接前端View和前端ViewModel,ViewModel再通过服务调用后端Model。 | Vue.js, Angular, Knockout.js。React + Hooks在某些场景下也接近MVVM。 |
核心结论:
- 前后端分离定义了系统层面的边界:前端 vs 后端。
- MVC、MVP、MVVM定义了前端应用内部的代码组织方式,即如何管理状态、处理交互和渲染视图。
你可以这样理解:在决定采用前后端分离这座“大楼”的结构后,你需要决定在前端这个“套房”里采用哪种“室内设计风格”(MVC/MVP/MVVM)。目前,MVVM无疑是这个领域最流行、最高效的“室内设计风格”。
为什么说 MVC、MVP、MVVM是前端或表现层内部的设计模式
这是一个非常好的问题,触及了对这些模式本质的理解。说MVC、MVP、MVVM是前端或表现层内部的设计模式,是因为它们都专注于解决同一个核心问题:如何组织用户界面(UI)的代码结构。
让我从几个关键角度来详细解释为什么:
1. 核心关注点:用户交互与数据展示
这些模式的核心关注点都是如何将数据呈现给用户,以及如何处理用户的输入。
- 它们都定义了"视图"的角色:负责显示信息给用户。
- 它们都定义了某种"模型"的角色:包含应用程序的数据和业务逻辑。
- 它们都定义了某种协调者角色(Controller/Presenter/ViewModel):负责在视图和模型之间进行协调。
这些职责完全属于"表现层"的范畴,与数据持久化、外部系统集成、核心算法等后端关注点截然不同。
2. 问题域的边界:从屏幕到业务逻辑的边界
想象一下应用程序的架构层次:
[用户] ↔ [UI界面] ↔ [表现层逻辑] ↔ [业务逻辑] ↔ [数据访问] ↔ [数据库]
MVC、MVP、MVVM主要活跃在 [UI界面] ↔ [表现层逻辑] 这个区间内。它们关心的是:
- 当用户点击按钮时,应该发生什么?
- 数据应该如何格式化以便在界面上显示?
- 界面状态(如加载中、错误提示)如何管理?
- 多个界面组件之间如何协调?
它们不关心:
- 数据如何从数据库查询(这是数据访问层的职责)
- 复杂的业务规则验证(这是业务逻辑层的职责)
- 如何与其他微服务通信(这是服务层的职责)
3. 技术实现的上下文
在具体的技术实现中,这些模式通常在前端框架或UI框架中体现:
- Web前端:React、Vue、Angular都提供了实现这些模式的机制
- 移动端:Android的Activity/Fragment可以看作View,ViewModel在MVVM中很常见
- 桌面端:WPF、Windows Forms都支持这些模式
这些框架的核心任务就是构建用户界面,因此它们内建了对这些表现层模式的支持。
4. 与后端模式的对比
为了更好地理解,可以对比一下后端的典型模式:
| 前端/表现层模式 | 后端/业务层模式 | 关注点 |
|---|---|---|
| MVC、MVP、MVVM | 分层架构 | UI组织 vs 业务代码组织 |
| 观察者模式(用于数据绑定) | 仓库模式 | UI更新 vs 数据访问抽象 |
| 组件模式 | 服务模式 | UI复用 vs 业务功能复用 |
| 命令模式 | 领域驱动设计 | 用户操作封装 vs 业务领域建模 |
5. 架构层面的定位
在清晰的架构分层中,这些模式的位置很明确:
┌─────────────────────────────────────────┐
│ 表现层 (Presentation Layer) │ ← MVC/MVP/MVVM活跃在这里
│ ┌─────────────┐ ┌──────────────────┐ │
│ │ View │ │ ViewModel/Presenter│ │
│ └─────────────┘ └──────────────────┘ │
│ │ │
└─────────────────┼────────────────────────┘
│
┌─────────────────┼────────────────────────┐
│ 业务逻辑层 (Business Layer) │ ← 包含领域模型、服务等
│ ┌─────────────┐ ┌──────────────────┐ │
│ │ Service │ │ Domain Model │ │
│ └─────────────┘ └──────────────────┘ │
└─────────────────────────────────────────┘
重要的澄清:关于"模型"的理解
这里有一个容易混淆的点:在这些模式中提到的"模型",与后端的业务模型是不同的:
- 表现层的模型:通常是视图模型或展示模型,包含的是界面直接需要的数据和格式
- 后端的业务模型:包含的是核心业务逻辑、数据验证规则等
在前后端分离的架构中,表现层从后端获取数据后,会将其转换为适合界面展示的格式,这本身就是MVVM中ViewModel的职责。
总结
说MVC、MVP、MVVM是前端或表现层内部的设计模式,是因为:
- 它们解决的问题域是用户界面的代码组织和数据流管理
- 它们的技术实现通常在前端框架或UI框架中
- 它们的关注点边界止于业务逻辑的入口
- 它们的核心价值在于提高UI代码的可维护性、可测试性和可扩展性
这些模式帮助开发者回答的是"如何更好地构建用户界面"这个问题,而不是"如何设计整个系统架构"。理解这一点对于在正确的地方使用正确的模式至关重要。
拉取数据与更新,有新发起http请求吗
这是一个非常关键的问题,它触及了MVC模式在不同架构上下文中的实现差异。答案取决于我们讨论的是哪种架构:传统服务端MVC还是前后端分离的MVC。
让我分两种情况详细解释:
情况一:传统服务端MVC(前后端不分离)
在这种架构下,整个过程只涉及一次HTTP请求-响应循环。
让我们回顾前面用户管理系统的例子,这次从HTTP请求的角度来看:
-
用户提交表单 → 浏览器发起 HTTP POST 请求 到服务器
POST /users/add Content-Type: application/x-www-form-urlencoded username=Charlie -
服务器端处理:
- Controller 接收请求,调用
model.addUser("Charlie") - Model 更新数据(可能在内存中,也可能写入数据库)
- Model 通知所有观察者(View)
- View 从Model获取最新数据并渲染HTML
- Controller 接收请求,调用
-
服务器返回响应 → 包含完整的新页面HTML
HTTP/1.1 200 OK Content-Type: text/html <html> <body> <table> <tr><td>Alice</td></tr> <tr><td>Bob</td></tr> <tr><td>Charlie</td></tr> ← 这里已经包含了新用户 </table> </body> </html>
关键点:
- 只有一次HTTP请求(表单提交)
- Model的更新和View的查询都在服务器端内存中完成,不涉及额外的HTTP调用
- 浏览器接收到的是完整的、已更新的页面
情况二:前后端分离架构中的MVC
组件结构:
- 前端Model: 管理前端状态(如
frontendUserList = ['Alice', 'Bob']) - 后端Model: 提供REST API的真正数据源
- View: React/Vue组件
- Controller: 前端的事件处理函数
交互流程:
- 用户提交表单 → 前端Controller处理
// 在前端Controller中 async addUser(username) { // 1. 调用后端API进行添加 - 第一次HTTP请求 const response = await fetch('/api/users', { method: 'POST', body: JSON.stringify({ name: username }) }); // 2. 添加成功后,重新获取完整列表 - 第二次HTTP请求 const updatedUsers = await fetch('/api/users').then(r => r.json()); // 3. 更新前端Model this.model.setUsers(updatedUsers); // 4. Model通知View,View自动更新 }
或者更优化的单次请求方式:
async addUser(username) {
// 1. 调用后端API进行添加
const response = await fetch('/api/users', {
method: 'POST',
body: JSON.stringify({ name: username })
});
// 2. 后端直接返回更新后的完整数据
const updatedUsers = await response.json();
// 3. 更新前端Model
this.model.setUsers(updatedUsers);
// 4. View自动更新
}
关键点:
- 通常需要1-2次HTTP请求
- Model更新通过POST/PUT请求完成
- 数据查询可能需要额外的GET请求,或者由后端在更新响应中直接返回最新数据
- 浏览器端只更新变化的部分,不是整个页面刷新
总结对比
| 方面 | 传统服务端MVC | 前后端分离MVC |
|---|---|---|
| HTTP请求次数 | 1次(表单提交) | 1-2次(更新 + 可选的数据重新获取) |
| Model更新 | 服务器端方法调用 | HTTP POST/PUT 请求到后端API |
| 数据查询 | 服务器端内存访问 | HTTP GET 请求到后端API(或从更新响应中获取) |
| View更新 | 服务器端渲染完整HTML | 前端JavaScript局部更新DOM |
| 性能影响 | 整个页面刷新 | 局部更新,用户体验更好 |
核心结论
在经典MVC理论中讨论的"Model更新通知View,View拉取数据"这一过程,指的是应用程序内部的对象间通信,不涉及网络请求。
- 在传统架构中:这发生在服务器内存中,M和V是同一个应用中的对象
- 在分离架构中:M被拆分为了"前端状态"和"后端API","拉取数据"这个过程就具体化为了HTTP API调用
所以,当你说"Model的更新是Controller中的添加用户接口,而查询是另外一个查询接口"时:
- 在传统MVC中,这两个"接口"都是同一个服务器应用内部的方法调用
- 在分离架构中,这两个"接口"很可能就是两个不同的HTTP API端点
理解这个区别对于正确应用MVC模式至关重要。
更多推荐



所有评论(0)