14.1 模型-视图-控制器

把界面交互拆成模型、视图和控制器三种职责,使输入处理、领域状态和呈现可以独立变化。

学习目标

  • 能解释模型、视图和控制器如何分离输入处理、业务状态与呈现,并画出请求到响应的责任链
  • 能编写 TypeScript MVC 控制器,注入模型服务和视图,覆盖成功与错误响应
  • 能根据交互复杂度、状态归属、视图变体和测试隔离,判断何时采用或拒绝 MVC

为什么 14.1 模型-视图-控制器 值得单独学习

把界面交互拆成模型、视图和控制器三种职责,使输入处理、领域状态和呈现可以独立变化。14.1 模型-视图-控制器的核心不是套用某个框架 API,而是回答:如何分开请求解释、领域调用与输出渲染,使变化不会横跨所有页面。在订单后台多视图中,如果无法说清责任、状态和失败由谁承担,即使正常请求能够返回,架构决定也没有完成。

的价值不是强迫每个页面拥有三层文件,而是让输入边界和领域状态的所有权可测试。本页以 2024 年中文版公开目录限定 14.1 模型-视图-控制器 的范围,并依据 Martin Fowler 的作者图书页和模式目录独立重写。它不复现原书正文、插图或代码;目录只决定“要讲什么”,这里的案例、实验、判断题和答案均为本课程原创。

先建立直觉:问题、机制与代价

14.1 模型-视图-控制器面对的具体压力是“路由数量、重复控制逻辑、视图变体、导航状态与输入错误”。它采用的机制可以概括为:控制器解释请求并调用模型,视图只把模型状态转换为输出。机制带来的收益必须与新增间接层、状态同步和模板复杂度同时记录,否则学习者只会得到一个没有拒绝条件的模式名称。

对 14.1 模型-视图-控制器,优先比较的替代路线是:页面控制器、前端控制器、模板视图、转换器或两步视图。只有在请求处理、业务状态和呈现确实有不同变化轴时,才值得引入 MVC;若直接执行业务规则或拼接持久化查询,就不能据此选择 14.1 模型-视图-控制器。

目录单元到教学证据

14.1 模型-视图-控制器

14.1 模型-视图-控制器 的学习边界里,14.1 模型-视图-控制器不是待背诵的目录词,而是用来检查 HTTP 请求是否把责任交给正确对象。对订单后台多视图,学习者要记录路由、输入校验和视图替换的可观察变化,并说明它何时支持或否定 14.1 模型-视图-控制器。

专属设计案例:订单后台多视图

把订单后台多视图切成“HTTP 请求 → 控制器 → 应用模型 → 视图 → HTTP 响应”五个观察点。14.1 模型-视图-控制器的设计草案必须写出谁拥有状态、谁作出业务决定、失败怎样传播,以及路由、输入校验、导航、模板转义、视图复用中哪个指标最先提示当前方案不再适用。

设计记录采用五个可换行字段:单元键为 poeaa24-pattern-25-model-view-controller;模式族为 web;裁决是“能替换一种视图而不改模型,注入控制器测试请求,并证明三者依赖方向清楚”;观测项包括路由、输入校验、导航、模板转义、视图复用;拒绝条件是“视图或控制器开始拥有不可测试的业务状态”。

配置不是生产框架语法,而是一张评审卡。对 14.1 模型-视图-控制器的任何实现都要能把运行证据重新映射到这张卡;如果更换 Web 框架或部署平台后无法回答同一组问题,说明决定依赖的是工具偶然行为而不是模式语义。

结构解剖

MVC:请求 → Controller → Model → View → 响应ModelViewController用户操作① 调用业务方法变更② 通知变更③ 读取状态渲染④ 返回 HTMLController 接收输入 → Model 处理业务 → View 渲染输出,三者解耦
MVC 将输入处理(Controller)、业务状态(Model)和输出渲染(View)分离。 Model 变更通知 View 刷新,View 可独立替换而不影响业务逻辑。

选择与拒绝矩阵

评审问题选择 14.1 模型-视图-控制器 的证据应拒绝或改用其他方案的信号
责任控制器解释输入,模型拥有状态,视图负责输出模板直接访问数据库并修改订单状态
变化可替换 HTML、JSON 或移动视图而不改业务模型视图变化迫使模型加入格式分支
失败输入错误、业务错误和渲染错误可分别观测所有异常都在控制器中返回空页面
替代已与前端控制器、页面控制器和两步视图比较仅因框架默认目录就机械拆成三层

代码实践:注入模型与视图的订单控制器

控制器只负责解析请求、调用模型服务并选择视图;模型服务不应知道 HTML,视图也不应重新决定业务状态。必须在控制器和视图之间以稳定数据传递。

type Request = {
  method: "GET" | "POST";
  path: string;
  body: { status?: "paid" | "open" };
};
 
type OrderModel = {
  id: string;
  status: "paid" | "open";
};
 
interface OrderService {
  load(id: string): OrderModel | undefined;
  changeStatus(id: string, status: "paid" | "open"): OrderModel;
}
 
interface OrderView {
  render(model: OrderModel): string;
  renderNotFound(): string;
}
 
class HtmlOrderView implements OrderView {
  render(model: OrderModel): string {
    return `<main data-order-id="${model.id}">${model.status}</main>`;
  }
 
  renderNotFound(): string {
    return "<main>not found</main>";
  }
}
 
class OrderController {
  constructor(
    private readonly service: OrderService,
    private readonly view: OrderView,
  ) {}
 
  handle(request: Request): string {
    const id = request.path.split("/").at(-1);
    if (!id) return this.view.renderNotFound();
 
    const model =
      request.method === "POST" && request.body.status
        ? this.service.changeStatus(id, request.body.status)
        : this.service.load(id);
    return model ? this.view.render(model) : this.view.renderNotFound();
  }
}

这段代码可以把 HtmlOrderView 替换成 JSON 视图,而不改变控制器的模型调用。真实项目还应对输出做模板转义、对请求做集中校验,并让服务层处理权限、事务和业务规则;控制器测试只需注入假的 OrderServiceOrderView

常见误区

可验证练习

练习

问题 1:替换视图。 订单后台要同时提供 HTML 和 JSON 输出,应该修改模型吗?

问题 2:检查责任。 OrderController 直接执行 SQL 查询后把数据库行传给模板,违反了哪些边界?

问题 3:选择替代方案。 单页应用已经由前端路由和状态容器管理所有交互,服务器只提供资源接口,还要强行使用服务器端 MVC 吗?

名词解释

名词解释

本章出现的专业名词,用大白话再讲一遍。

模型-视图-控制器

把输入控制、应用或领域状态、输出呈现分成可独立变化职责的交互结构。

控制器边界

接收请求、校验输入、选择应用动作并决定响应的边界对象。

模型状态

由业务规则决定且不依赖某种输出格式的当前业务状态。

本章小结

掌握 14.1 模型-视图-控制器的标志不是记住定义,而是能在订单后台多视图中解释“HTTP 请求 → 控制器 → 应用模型 → 视图 → HTTP 响应”的责任链,利用路由、输入校验、导航、模板转义、视图复用作出可证伪的选择,并在模板直接执行业务规则或数据库访问时明确拒绝。

前后导航

来源与改写范围

资料与写作方式声明

本章以Martin Fowler《企业应用架构模式》与公开模式目录权威目录界定学习范围,并结合正文列出的技术资料独立重写;不宣称复现原书正文,也不沿用原作表述。

原作版权归作者与出版社所有;本站原创教学结构与表述仅供学习交流。

讨论

评论区加载中…