在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

这三种模式共同的目标都是分离关注点,尤其是将用户界面(UI)逻辑与业务逻辑和数据分离,以提高代码的可维护性、可测试性和可扩展性。


1. MVC(Model-View-Controller)

核心思想: 用户请求直接到达控制器,由控制器协调模型和视图。

组件职责:

  • 模型: 代表应用程序状态和核心业务逻辑。它独立于UI,可以响应状态查询,处理业务流程,并通知视图状态已更新。
  • 视图: 负责呈现模型的数据给用户。它显示模型状态,并将用户输入数据传给控制器(而非直接与模型交互)。
  • 控制器: 作为中间人。它接受用户请求调用模型来完成业务处理,然后选择视图来显示响应结果。

数据流向(基于图13-2):

  1. 用户与视图交互(如点击按钮),产生用户请求
  2. 请求被发送到控制器
  3. 控制器调用模型进行业务处理
  4. 模型处理完成后,通知视图有数据更新。
  5. 视图接收到通知后,向模型进行状态查询,获取最新数据并更新显示。

关键特点:

  • 视图和模型之间存在直接通信(视图监听模型的更新通知)。这在现代应用开发中有时会导致视图和模型耦合过紧。

应用场景:

  • 传统的Web应用框架(如Spring MVC, ASP.NET MVC)。
  • 桌面应用程序。
  • 适用于中等复杂度的项目,其中视图与模型的依赖关系相对直接。

2. MVP(Model-View-Presenter)

核心思想: 用户输入先传递给视图,视图再将所有交互委托给Presenter。Presenter是控制中心,完全解耦了视图和模型。

组件职责:

  • 模型: 与MVC中的模型职责相同,代表数据和业务逻辑。
  • 视图: 变得非常“被动”。它只负责渲染UI和接收用户输入,然后将所有输入转发给Presenter。在图中,它甚至作为“改变的存储”,意味着它可能持有Presenter需要的数据。
  • Presenter: 是中间人和控制者。它从视图接收用户动作更新模型,然后从模型获取更新后的数据,最后主动更新视图

数据流向(基于图13-3):

  1. 用户在视图上执行操作
  2. 视图将动作通知Presenter
  3. Presenter更新模型
  4. Presenter从模型获取更新后的数据
  5. 模型将变化通知Presenter(或其他中间件,但最终由Presenter处理)。
  6. 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及常见理解):

  1. 视图通过数据绑定自动显示ViewModel中的数据。
  2. 用户与视图交互(如点击),触发绑定在ViewModel上的命令
  3. ViewModel执行命令,可能会调用模型进行业务处理。
  4. 模型更新后,通过通知机制(如事件、Observable)告知ViewModel。
  5. ViewModel更新自身的数据属性。
  6. 由于数据绑定,视图自动更新以反映ViewModel的最新状态。

关键特点:

  • 双向数据绑定是核心,它极大地减少了“胶水代码”。
  • ViewModel不知道视图的存在,实现了更彻底的解耦。
  • 视图的主动控制器(如Controller/Presenter)被数据绑定框架取代,开发者的工作重心从控制UI元素转向声明数据关系和命令。

应用场景:

  • 支持数据绑定的现代UI框架,如 WPFSilverlightAngularVue.jsKnockout.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更新。

关系:

  1. ViewModel通过服务层(如一个专门的API调用模块) 去与后端Model(API)交互。
  2. 获取到数据后,更新ViewModel自身的响应式数据属性。
  3. 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是前端或表现层内部的设计模式,是因为:

  1. 它们解决的问题域是用户界面的代码组织和数据流管理
  2. 它们的技术实现通常在前端框架或UI框架中
  3. 它们的关注点边界止于业务逻辑的入口
  4. 它们的核心价值在于提高UI代码的可维护性、可测试性和可扩展性

这些模式帮助开发者回答的是"如何更好地构建用户界面"这个问题,而不是"如何设计整个系统架构"。理解这一点对于在正确的地方使用正确的模式至关重要。

拉取数据与更新,有新发起http请求吗

这是一个非常关键的问题,它触及了MVC模式在不同架构上下文中的实现差异。答案取决于我们讨论的是哪种架构:传统服务端MVC还是前后端分离的MVC。

让我分两种情况详细解释:


情况一:传统服务端MVC(前后端不分离)

在这种架构下,整个过程只涉及一次HTTP请求-响应循环

让我们回顾前面用户管理系统的例子,这次从HTTP请求的角度来看:

  1. 用户提交表单 → 浏览器发起 HTTP POST 请求 到服务器

    POST /users/add
    Content-Type: application/x-www-form-urlencoded
    
    username=Charlie
    
  2. 服务器端处理

    • Controller 接收请求,调用 model.addUser("Charlie")
    • Model 更新数据(可能在内存中,也可能写入数据库)
    • Model 通知所有观察者(View)
    • View 从Model获取最新数据并渲染HTML
  3. 服务器返回响应 → 包含完整的新页面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: 前端的事件处理函数
交互流程:
  1. 用户提交表单 → 前端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模式至关重要。

Logo

这里是“一人公司”的成长家园。我们提供从产品曝光、技术变现到法律财税的全栈内容,并连接云服务、办公空间等稀缺资源,助你专注创造,无忧运营。

更多推荐