Skip to content

MVP

MVP(Model-View-Presenter) 是一种从 MVC 模式衍生出来的软件架构模式,主要用于分离界面(View)和业务逻辑(Model),提高代码的可测试性和可维护性。它特别适用于需要严格分离视图层和业务逻辑的应用场景,如移动应用开发和前端框架。

核心组件

  1. 模型(Model)

    • 负责数据管理和业务逻辑,与 MVC 中的 Model 类似。
    • 通常包含数据访问、验证和处理等功能。
  2. 视图(View)

    • 负责 UI 展示,不包含任何业务逻辑。
    • 通常是被动的(Passive View),仅显示 Presenter 提供的数据。
    • 向 Presenter 发送用户交互事件(如点击、输入)。
  3. Presenter

    • 作为 View 和 Model 之间的桥梁,处理所有业务逻辑。
    • 接收 View 的用户事件,调用 Model 处理数据。
    • 根据 Model 的状态更新 View。

与 MVC 的区别

特性MVCMVP
View 与 Model 的关系直接交互(双向依赖)通过 Presenter 间接交互(单向依赖)
Controller 的职责处理用户请求,协调 Model 和 ViewPresenter 承担更多业务逻辑,与 View 强耦合
测试难度较难(View 和 Model 耦合)较易(Presenter 可独立测试)
数据流双向(复杂)单向(Presenter → View)

工作流程

MVP 的数据流通常遵循以下步骤:

  1. 用户交互:用户通过 View 触发操作(如点击按钮)。
  2. View 通知 Presenter:View 将事件传递给 Presenter。
  3. Presenter 处理逻辑:Presenter 调用 Model 处理数据。
  4. Model 返回结果:Model 执行操作并返回数据。
  5. Presenter 更新 View:Presenter 根据结果更新 View 的状态。
用户 → View → Presenter → Model → Presenter → View(更新)

示例:待办事项应用(MVP 实现)

以下是一个简化的 MVP 实现示例(使用 JavaScript):

1. 模型(Model)

javascript
class TodoModel {
  constructor() {
    this.todos = [];
  }

  // 添加待办事项
  addTodo(text) {
    const todo = { id: Date.now(), text, completed: false };
    this.todos.push(todo);
    return todo;
  }

  // 删除待办事项
  removeTodo(id) {
    this.todos = this.todos.filter((todo) => todo.id !== id);
  }

  // 获取所有待办事项
  getAll() {
    return [...this.todos];
  }
}

2. 视图接口(View Interface)

javascript
// 定义View必须实现的方法
class TodoView {
  constructor(presenter) {
    this.presenter = presenter;
    this.initDOM();
    this.bindEvents();
  }

  // 初始化DOM元素
  initDOM() {
    this.container = document.createElement("div");
    this.container.innerHTML = `
      <input type="text" id="todo-input" placeholder="添加待办事项">
      <button id="add-button">添加</button>
      <ul id="todo-list"></ul>
    `;
  }

  // 绑定事件到Presenter
  bindEvents() {
    document.getElementById("add-button").addEventListener("click", () => {
      const input = document.getElementById("todo-input");
      this.presenter.onAddTodo(input.value);
      input.value = "";
    });
  }

  // 更新视图显示
  displayTodos(todos) {
    const todoList = document.getElementById("todo-list");
    todoList.innerHTML = "";

    todos.forEach((todo) => {
      const li = document.createElement("li");
      li.textContent = todo.text;
      li.innerHTML += ` <button data-id="${todo.id}">删除</button>`;
      li.querySelector("button").addEventListener("click", () => {
        this.presenter.onRemoveTodo(todo.id);
      });
      todoList.appendChild(li);
    });
  }

  // 获取DOM元素供外部使用
  getElement() {
    return this.container;
  }
}

3. Presenter

javascript
class TodoPresenter {
  constructor(model, view) {
    this.model = model;
    this.view = view;
    this.updateView();
  }

  // 添加待办事项
  onAddTodo(text) {
    if (text.trim()) {
      this.model.addTodo(text);
      this.updateView();
    }
  }

  // 删除待办事项
  onRemoveTodo(id) {
    this.model.removeTodo(id);
    this.updateView();
  }

  // 更新视图
  updateView() {
    const todos = this.model.getAll();
    this.view.displayTodos(todos);
  }
}

4. 初始化应用

javascript
// 创建MVP实例并关联
const model = new TodoModel();
const view = new TodoView();
const presenter = new TodoPresenter(model, view);

// 将视图添加到DOM
document.body.appendChild(view.getElement());

MVP 的变体

  1. 被动视图(Passive View)

    • View 完全被动,所有逻辑在 Presenter 中实现。
    • 优点:可测试性高,View 简单易维护。
    • 缺点:Presenter 可能变得庞大。
  2. 监督控制器(Supervising Controller)

    • View 处理简单逻辑(如格式转换),复杂逻辑在 Presenter 中。
    • 优点:减轻 Presenter 负担,提高性能。
    • 缺点:部分逻辑分散在 View 中,测试略复杂。

优缺点

优点缺点
分离视图和业务逻辑,提高可测试性Presenter 可能变得庞大(Massive Presenter)
支持视图复用(同一 Presenter 可驱动多个 View)代码量增加,架构复杂度提高
便于单元测试(Presenter 可独立测试)视图更新依赖 Presenter,可能导致性能问题
适合 TDD(测试驱动开发)学习曲线较陡,小型应用可能不适用

适用场景

  • 需要严格分离视图和业务逻辑的应用。
  • 复杂 UI 交互场景,需要提高代码可维护性。
  • 需要支持多平台视图(如 Web、移动端)共享业务逻辑。
  • 测试驱动开发(TDD)环境,Presenter 可独立测试。

总结

MVP 通过引入 Presenter 作为 View 和 Model 的中间层,有效解决了 MVC 中视图与模型紧耦合的问题,提高了代码的可测试性和可维护性。虽然它增加了一定的架构复杂度,但在大型应用或需要频繁修改 UI 的场景中,MVP 是一种非常有价值的选择。

Released under the MIT License.