浅析 MVC

200 阅读4分钟

浅析 MVC

MVC 是一种软件设计模式,它将应用程序分为三个部分:模型(Model)、视图(View)和控制器(Controller)。MVC 的目的是实现关注点分离,即每个部分只负责自己的功能,而不干涉其他部分的逻辑。这样可以提高代码的可维护性、可复用性和可测试性。

模型(Model)

模型是应用程序的核心部分,它负责处理数据和业务逻辑。模型通常与数据库或其他数据源交互,提供数据的增删改查等操作。模型也可以定义一些事件,当数据发生变化时,通知其他部分更新。

例如,一个用户模型可能有以下属性和方法:

class User {
  constructor(name, age, email) {
    this.name = name;
    this.age = age;
    this.email = email;
  }

  // 保存用户到数据库
  save() {
    // 调用数据库 API
    db.insert(this);
    // 触发事件
    EventBus.emit('userSaved', this);
  }

  // 从数据库删除用户
  delete() {
    // 调用数据库 API
    db.delete(this);
    // 触发事件
    EventBus.emit('userDeleted', this);
  }

  // 更新用户信息
  update(data) {
    // 调用数据库 API
    db.update(this, data);
    // 触发事件
    EventBus.emit('userUpdated', this);
  }

  // 查询用户信息
  static find(query) {
    // 调用数据库 API
    return db.find(query);
  }
}

视图(View)

视图是应用程序的界面部分,它负责展示数据和接收用户输入。视图通常由 HTML、CSS 和 JavaScript 组成,可以使用各种框架或库来实现。视图可以订阅模型的事件,当数据变化时,更新界面。视图也可以触发控制器的方法,当用户操作时,执行相应的逻辑。

例如,一个用户列表视图可能有以下代码:

<!-- HTML -->
<ul id="user-list">
  <!-- 动态生成用户列表 -->
</ul>
<button id="add-user">添加用户</button>
/* CSS */
ul {
  list-style: none;
}

li {
  margin: 10px;
}

button {
  margin: 10px;
}
// JavaScript

// 获取 DOM 元素
const userList = document.getElementById('user-list');
const addUser = document.getElementById('add-user');

// 渲染用户列表
function renderUserList(users) {
  // 清空原有内容
  userList.innerHTML = '';
  // 遍历用户数组
  for (let user of users) {
    // 创建 li 元素
    let li = document.createElement('li');
    // 设置 li 的内容
    li.textContent = `${user.name} (${user.age}) - ${user.email}`;
    // 添加删除按钮
    let deleteButton = document.createElement('button');
    deleteButton.textContent = '删除';
    deleteButton.addEventListener('click', () => {
      // 调用控制器方法
      controller.deleteUser(user);
    });
    li.appendChild(deleteButton);
    // 添加 li 到 ul 中
    userList.appendChild(li);
  }
}

// 订阅模型事件
EventBus.on('userSaved', (user) => {
  // 获取当前用户列表
  let users = User.find();
  // 渲染用户列表
  renderUserList(users);
});

EventBus.on('userDeleted', (user) => {
  // 获取当前用户列表
  let users = User.find();
  // 渲染用户列表
  renderUserList(users);
});

EventBus.on('userUpdated', (user) => {
  // 获取当前用户列表
  let users = User.find();
  // 渲染用户列表
  renderUserList(users);
});

// 添加用户按钮的点击事件
addUser.addEventListener('click', () => {
  // 调用控制器方法
  controller.addUser();
});

控制器(Controller)

控制器是应用程序的中间部分,它负责协调模型和视图之间的交互。控制器通常定义一些方法,根据用户输入或其他条件,调用模型的操作或更新视图的显示。控制器可以实现一些业务逻辑,例如验证、过滤、转换等。

例如,一个用户控制器可能有以下方法:

class UserController {
  // 添加用户
  addUser() {
    // 获取用户输入
    let name = prompt('请输入姓名');
    let age = prompt('请输入年龄');
    let email = prompt('请输入邮箱');
    // 验证输入
    if (name && age && email) {
      // 创建用户对象
      let user = new User(name, age, email);
      // 调用模型方法
      user.save();
    } else {
      // 提示错误
      alert('请输入完整信息');
    }
  }

  // 删除用户
  deleteUser(user) {
    // 确认操作
    if (confirm(`确定要删除 ${user.name} 吗?`)) {
      // 调用模型方法
      user.delete();
    }
  }
}

// 创建控制器实例
const controller = new UserController();

EventBus

EventBus 是一个事件总线,它提供了一种发布-订阅模式的通信机制。EventBus 可以让不同的组件之间解耦,通过事件来传递数据和消息。EventBus 有以下几个常用的 API:

  • emit(event, data):触发一个事件,并传递一些数据。
  • on(event, callback):订阅一个事件,并指定一个回调函数。
  • off(event, callback):取消订阅一个事件,并移除一个回调函数。
  • once(event, callback):订阅一个事件,并指定一个只执行一次的回调函数。

例如,我们可以使用 EventBus 来实现一个简单的计数器:

// 创建 EventBus 实例
const EventBus = new EventBus();

// 定义计数器模型
class Counter {
  constructor() {
    this.count = 0;
  }

  // 增加计数
  increase() {
    this.count++;
    // 触发事件
    EventBus.emit('countChanged', this.count);
  }

  // 减少计数
  decrease() {
    this.count--;
    // 触发事件
    EventBus.emit('countChanged', this.count);
  }
}

// 创建计数器实例
const counter = new Counter();

// 定义计数器视图
class CounterView {
  constructor() {
    // 获取 DOM 元素
    this.display = document.getElementById('display');
    this.increaseButton = document.getElementById('increase');
    this.decreaseButton = document.getElementById('decrease');
    // 添加按钮事件监听器
    this.increaseButton.addEventListener('click', () => {
      // 调用模型方法
      counter.increase();
    });
    this.decreaseButton.addEventListener('click', () => {
      // 调用模型方法
      counter.decrease();
    });
    // 订阅事件
    EventBus.on('countChanged', (count) => {
      // 更新显示
      this.display.textContent = count;
    });
  }
}

// 创建视图实例
const view = new CounterView();

表驱动编程

表驱动编程是一种编程技巧,它将一些逻辑或数据从代码中抽象出来,放到一个表格(数组、对象、哈希表等)中,然后通过查表来执行相应的操作。表驱动编程可以使代码更简洁、更易读、更易维护,也可以避免重复和硬编码。

例如,我们可以使用表驱动编程来实现一个简单的计算器:

// 定义操作符和对应的函数的映射表
const operators = {
  '+': (a, b) => a + b,
  '-': (a, b) => a - b,
  '*': (a, b) => a * b,
  '/': (a, b) => a / b,
};

// 定义一个计算函数,接受两个数字和一个操作符
function calculate(a, b, op) {
  // 查表获取对应的函数
  let func = operators[op];
  // 判断是否存在该函数
  if (func) {
    // 调用函数并返回结果
    return func(a, b);
  } else {
    // 抛出异常
    throw new Error('Invalid operator');
  }
}

// 测试
console.log(calculate(1, 2, '+')); // 3
console.log(calculate(3, 4, '*')); // 12
console.log(calculate(5, 6, '%')); // Error: Invalid operator

模块化的理解

模块化是一种编程思想,它将一个复杂的程序分解为多个相互独立的模块,每个模块只负责一部分功能,而不影响其他模块。模块化可以提高代码的可读性、可复用性、可扩展性和可测试性,也可以降低代码的耦合度和复杂度。

个人认为,模块化有以下几个要点:

  • 模块应该有明确的职责和边界,不要做过多或过少的事情。
  • 模块应该有清晰的接口,定义好输入和输出,以及可能抛出的异常。
  • 模块应该尽量遵循单一职责原则、开闭原则、里氏替换原则等设计原则。
  • 模块应该尽量减少对外部的依赖,使用依赖注入、抽象工厂等方式来解决依赖问题。
  • 模块应该有良好的文档和注释,说明模块的功能、用法、参数、返回值等信息。