跳到主要内容
预计阅读 52 分钟

行为型模式(下)—— 责任链、命令、状态、中介者、备忘录、访问者

这一章一口气介绍六个行为型模式。它们分别处理不同的协作难题:请求的逐级传递、操作的封装与撤销、状态驱动的行为切换、多对象之间的通信协调、状态的快照与恢复、以及对复杂数据结构的遍历操作。

💡 学习建议:本章信息密度较高,建议分两次阅读——前三个模式(责任链、命令、状态)为一组,后三个模式(中介者、备忘录、访问者)为一组,每组消化后再进入下一组。

📋 开篇自测:你已经知道多少?

  1. Express/Koa 的中间件机制背后对应的是什么设计模式?
  2. 文本编辑器的”撤销/重做”功能,通常用什么模式来实现?
  3. 一个角色在”正常”、“中毒”、“眩晕”三种状态下行为完全不同,你会怎么组织代码?

一、责任链模式:请求沿链条逐级传递

1.1 从表单验证管线说起

假设你在开发一个用户注册表单,提交前需要进行多项验证:检查必填项 → 检查格式合法性 → 检查用户名唯一性 → 检查密码强度。如果任何一项验证失败,就终止流程并返回错误信息。

// 反面教材:所有验证逻辑耦合在一起
function validateRegistration(form: RegistrationForm): string | null {
  // 必填检查
  if (!form.username) return '用户名不能为空';
  if (!form.email) return '邮箱不能为空';
  if (!form.password) return '密码不能为空';

  // 格式检查
  if (!/^[a-zA-Z0-9_]{3,20}$/.test(form.username)) return '用户名格式不正确';
  if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(form.email)) return '邮箱格式不正确';

  // 唯一性检查(需要异步查数据库,但这里是同步函数...)
  // 密码强度检查
  if (form.password.length < 8) return '密码至少 8 位';

  return null;
}

问题:所有验证逻辑混在一起,无法灵活地增删验证步骤。不同的表单(注册、修改资料、管理员创建用户)可能需要不同的验证组合,但现在无法复用单个验证环节。

责任链模式的核心思想是:将请求的发送者和接收者解耦,让多个处理者对象组成一条链,请求沿着链传递,直到被某个处理者处理或到达链末端。

1.2 责任链实现

interface ValidationResult {
  valid: boolean;
  error?: string;
}

interface RegistrationForm {
  username: string;
  email: string;
  password: string;
}

// 验证处理器抽象类
abstract class FormValidator {
  private next: FormValidator | null = null;

  setNext(validator: FormValidator): FormValidator {
    this.next = validator;
    return validator; // 支持链式设置
  }

  async handle(form: RegistrationForm): Promise<ValidationResult> {
    const result = await this.validate(form);
    if (!result.valid) {
      return result; // 验证失败,终止链条
    }
    // 验证通过,交给下一个处理者
    if (this.next) {
      return this.next.handle(form);
    }
    return { valid: true }; // 所有处理者都通过了
  }

  protected abstract validate(form: RegistrationForm): Promise<ValidationResult>;
}

// 具体处理者
class RequiredFieldValidator extends FormValidator {
  protected async validate(form: RegistrationForm): Promise<ValidationResult> {
    if (!form.username?.trim()) return { valid: false, error: '用户名不能为空' };
    if (!form.email?.trim()) return { valid: false, error: '邮箱不能为空' };
    if (!form.password) return { valid: false, error: '密码不能为空' };
    return { valid: true };
  }
}

class FormatValidator extends FormValidator {
  protected async validate(form: RegistrationForm): Promise<ValidationResult> {
    if (!/^[a-zA-Z0-9_]{3,20}$/.test(form.username)) {
      return { valid: false, error: '用户名只能包含字母、数字和下划线,长度 3-20' };
    }
    if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(form.email)) {
      return { valid: false, error: '邮箱格式不正确' };
    }
    return { valid: true };
  }
}

class PasswordStrengthValidator extends FormValidator {
  protected async validate(form: RegistrationForm): Promise<ValidationResult> {
    if (form.password.length < 8) {
      return { valid: false, error: '密码至少 8 位' };
    }
    if (!/[A-Z]/.test(form.password) || !/[0-9]/.test(form.password)) {
      return { valid: false, error: '密码必须包含大写字母和数字' };
    }
    return { valid: true };
  }
}

class UniqueUsernameValidator extends FormValidator {
  private existingUsers = new Set(['admin', 'root', 'test']);

  protected async validate(form: RegistrationForm): Promise<ValidationResult> {
    // 模拟异步数据库查询
    await new Promise(r => setTimeout(r, 100));
    if (this.existingUsers.has(form.username.toLowerCase())) {
      return { valid: false, error: `用户名 "${form.username}" 已被注册` };
    }
    return { valid: true };
  }
}

组装并使用:

// 组装验证链
const validator = new RequiredFieldValidator();
validator
  .setNext(new FormatValidator())
  .setNext(new PasswordStrengthValidator())
  .setNext(new UniqueUsernameValidator());

// 验证
const result = await validator.handle({
  username: 'alice',
  email: 'alice@example.com',
  password: 'MyPass123',
});
console.log(result); // { valid: true }

const result2 = await validator.handle({
  username: 'admin',
  email: 'admin@example.com',
  password: 'MyPass123',
});
console.log(result2); // { valid: false, error: '用户名 "admin" 已被注册' }

二、命令模式:操作的封装与撤销

2.1 从富文本编辑器说起

假设你在开发一个富文本编辑器,需要支持撤销和重做功能。用户的每一步操作(输入文字、加粗、插入图片、删除段落)都要能回退。

命令模式的核心思想是:将一个请求封装成一个对象,从而使你可以用不同的请求来参数化客户端,支持排队、记录日志、撤销等操作。

2.2 命令模式实现

// 命令接口
interface EditorCommand {
  execute(): void;
  undo(): void;
  getDescription(): string;
}

// 编辑器状态
class TextDocument {
  private content: string[] = [];

  getContent(): string { return this.content.join(''); }
  getLines(): string[] { return [...this.content]; }

  insertAt(position: number, text: string): void {
    this.content.splice(position, 0, text);
  }

  removeAt(position: number, count: number): string[] {
    return this.content.splice(position, count);
  }

  replaceAt(position: number, text: string): string {
    const old = this.content[position];
    this.content[position] = text;
    return old;
  }
}

// 具体命令:插入文本
class InsertTextCommand implements EditorCommand {
  constructor(
    private doc: TextDocument,
    private position: number,
    private text: string
  ) {}

  execute(): void {
    this.doc.insertAt(this.position, this.text);
  }

  undo(): void {
    this.doc.removeAt(this.position, 1);
  }

  getDescription(): string {
    return `插入 "${this.text.substring(0, 20)}${this.text.length > 20 ? '...' : ''}"`;
  }
}

// 具体命令:删除文本
class DeleteTextCommand implements EditorCommand {
  private deletedContent: string[] = [];

  constructor(
    private doc: TextDocument,
    private position: number,
    private count: number
  ) {}

  execute(): void {
    this.deletedContent = this.doc.removeAt(this.position, this.count);
  }

  undo(): void {
    // 恢复被删除的内容
    this.deletedContent.forEach((text, i) => {
      this.doc.insertAt(this.position + i, text);
    });
  }

  getDescription(): string {
    return `删除 ${this.count} 个字符`;
  }
}

// 具体命令:文本加粗
class BoldTextCommand implements EditorCommand {
  private previousText = '';

  constructor(
    private doc: TextDocument,
    private position: number
  ) {}

  execute(): void {
    const lines = this.doc.getLines();
    if (this.position < lines.length) {
      this.previousText = lines[this.position];
      this.doc.replaceAt(this.position, `<b>${this.previousText}</b>`);
    }
  }

  undo(): void {
    this.doc.replaceAt(this.position, this.previousText);
  }

  getDescription(): string { return '加粗文本'; }
}

// 命令管理器:支持撤销/重做
class CommandHistory {
  private history: EditorCommand[] = [];
  private undoneStack: EditorCommand[] = [];

  executeCommand(command: EditorCommand): void {
    command.execute();
    this.history.push(command);
    this.undoneStack = []; // 新操作清空重做栈
    console.log(`执行: ${command.getDescription()}`);
  }

  undo(): void {
    const command = this.history.pop();
    if (command) {
      command.undo();
      this.undoneStack.push(command);
      console.log(`撤销: ${command.getDescription()}`);
    } else {
      console.log('没有可撤销的操作');
    }
  }

  redo(): void {
    const command = this.undoneStack.pop();
    if (command) {
      command.execute();
      this.history.push(command);
      console.log(`重做: ${command.getDescription()}`);
    } else {
      console.log('没有可重做的操作');
    }
  }
}

使用:

const doc = new TextDocument();
const history = new CommandHistory();

history.executeCommand(new InsertTextCommand(doc, 0, 'Hello'));
history.executeCommand(new InsertTextCommand(doc, 1, ' World'));
history.executeCommand(new BoldTextCommand(doc, 0));

console.log(doc.getContent()); // <b>Hello</b> World

history.undo(); // 撤销加粗
console.log(doc.getContent()); // Hello World

history.undo(); // 撤销插入 " World"
console.log(doc.getContent()); // Hello

history.redo(); // 重做插入 " World"
console.log(doc.getContent()); // Hello World

三、状态模式:让对象在不同状态下表现不同

3.1 从工单流转系统说起

一个客服工单有多种状态:待受理、处理中、待评审、已完成、已关闭。不同状态下,同一操作的行为不同——处理中的工单可以提交评审,但已关闭的工单不能再操作。

状态模式的核心思想是:允许一个对象在其内部状态改变时改变它的行为,使对象看起来好像修改了它的类。

3.2 状态模式实现

// 状态接口
interface TicketState {
  getName(): string;
  assign(ticket: SupportTicket, agent: string): void;
  resolve(ticket: SupportTicket, solution: string): void;
  review(ticket: SupportTicket, approved: boolean): void;
  close(ticket: SupportTicket, reason: string): void;
}

class SupportTicket {
  private state: TicketState;
  agent: string | null = null;
  solution: string | null = null;

  constructor(public readonly id: string, public readonly title: string) {
    this.state = new PendingState();
  }

  setState(state: TicketState): void {
    console.log(`工单 ${this.id} 状态变更: ${this.state.getName()} → ${state.getName()}`);
    this.state = state;
  }

  getStateName(): string { return this.state.getName(); }

  // 委托给当前状态对象
  assign(agent: string): void { this.state.assign(this, agent); }
  resolve(solution: string): void { this.state.resolve(this, solution); }
  review(approved: boolean): void { this.state.review(this, approved); }
  close(reason: string): void { this.state.close(this, reason); }
}

// 具体状态:待受理
class PendingState implements TicketState {
  getName(): string { return '待受理'; }

  assign(ticket: SupportTicket, agent: string): void {
    ticket.agent = agent;
    ticket.setState(new InProgressState());
    console.log(`客服 ${agent} 接手工单`);
  }

  resolve(): void { console.log('操作无效:工单尚未受理,不能直接解决'); }
  review(): void { console.log('操作无效:工单尚未受理'); }

  close(ticket: SupportTicket, reason: string): void {
    ticket.setState(new ClosedState());
    console.log(`工单关闭: ${reason}`);
  }
}

// 具体状态:处理中
class InProgressState implements TicketState {
  getName(): string { return '处理中'; }

  assign(ticket: SupportTicket, agent: string): void {
    ticket.agent = agent;
    console.log(`工单转交给 ${agent}`);
  }

  resolve(ticket: SupportTicket, solution: string): void {
    ticket.solution = solution;
    ticket.setState(new ReviewState());
    console.log(`已提交解决方案,等待评审`);
  }

  review(): void { console.log('操作无效:尚未提交解决方案'); }

  close(ticket: SupportTicket, reason: string): void {
    ticket.setState(new ClosedState());
    console.log(`工单关闭: ${reason}`);
  }
}

// 具体状态:待评审
class ReviewState implements TicketState {
  getName(): string { return '待评审'; }

  assign(): void { console.log('操作无效:评审中不能转交'); }
  resolve(): void { console.log('操作无效:已提交方案,请等待评审'); }

  review(ticket: SupportTicket, approved: boolean): void {
    if (approved) {
      ticket.setState(new CompletedState());
      console.log('评审通过,工单已完成');
    } else {
      ticket.setState(new InProgressState());
      console.log('评审未通过,退回处理');
    }
  }

  close(ticket: SupportTicket, reason: string): void {
    ticket.setState(new ClosedState());
    console.log(`工单关闭: ${reason}`);
  }
}

// 具体状态:已完成
class CompletedState implements TicketState {
  getName(): string { return '已完成'; }

  assign(): void { console.log('操作无效:工单已完成'); }
  resolve(): void { console.log('操作无效:工单已完成'); }
  review(): void { console.log('操作无效:工单已完成'); }

  close(ticket: SupportTicket, reason: string): void {
    ticket.setState(new ClosedState());
    console.log(`工单归档关闭: ${reason}`);
  }
}

// 具体状态:已关闭
class ClosedState implements TicketState {
  getName(): string { return '已关闭'; }

  assign(): void { console.log('操作无效:工单已关闭'); }
  resolve(): void { console.log('操作无效:工单已关闭'); }
  review(): void { console.log('操作无效:工单已关闭'); }
  close(): void { console.log('操作无效:工单已关闭'); }
}

使用:

const ticket = new SupportTicket('TK-001', '页面加载缓慢');

ticket.assign('Alice');           // 客服 Alice 接手
ticket.resolve('优化了数据库查询'); // 提交方案
ticket.review(false);              // 评审未通过,退回
ticket.resolve('增加了索引和缓存'); // 重新提交
ticket.review(true);               // 评审通过
ticket.close('客户确认问题已修复');  // 归档

四、中介者模式:解耦多对象之间的通信

4.1 从聊天室说起

假设你在开发一个在线聊天室。如果每个用户对象都直接引用其他所有用户来发送消息,当用户数增长时,对象之间的关系会变成一张复杂的网。

中介者模式的核心思想是:用一个中介对象来封装一组对象之间的交互,使这些对象不需要直接引用彼此。

interface ChatMediator {
  register(user: ChatUser): void;
  sendMessage(message: string, sender: ChatUser, targetRoom: string): void;
  sendPrivate(message: string, sender: ChatUser, targetName: string): void;
}

class ChatRoom implements ChatMediator {
  private users = new Map<string, ChatUser>();

  register(user: ChatUser): void {
    this.users.set(user.name, user);
    console.log(`${user.name} 加入了聊天室`);
    // 通知其他人
    this.users.forEach((u) => {
      if (u !== user) u.receive(`系统消息: ${user.name} 加入了聊天室`, 'system');
    });
  }

  sendMessage(message: string, sender: ChatUser, targetRoom: string): void {
    this.users.forEach((user) => {
      if (user !== sender) {
        user.receive(`[${targetRoom}] ${sender.name}: ${message}`, sender.name);
      }
    });
  }

  sendPrivate(message: string, sender: ChatUser, targetName: string): void {
    const target = this.users.get(targetName);
    if (target) {
      target.receive(`[私聊] ${sender.name}: ${message}`, sender.name);
    } else {
      sender.receive(`系统消息: 用户 ${targetName} 不在线`, 'system');
    }
  }
}

class ChatUser {
  constructor(
    public readonly name: string,
    private mediator: ChatMediator
  ) {
    mediator.register(this);
  }

  send(message: string, room = 'general'): void {
    console.log(`${this.name} 发送: ${message}`);
    this.mediator.sendMessage(message, this, room);
  }

  sendPrivateTo(message: string, targetName: string): void {
    this.mediator.sendPrivate(message, this, targetName);
  }

  receive(message: string, from: string): void {
    console.log(`  ${this.name} 收到 [来自 ${from}]: ${message}`);
  }
}

使用:

const chatRoom = new ChatRoom();
const alice = new ChatUser('Alice', chatRoom);
const bob = new ChatUser('Bob', chatRoom);
const charlie = new ChatUser('Charlie', chatRoom);

alice.send('大家好!');
bob.sendPrivateTo('嗨 Alice,周五有空吗?', 'Alice');

每个 ChatUser 只和 ChatRoom 打交道,不需要知道其他用户的存在。


五、备忘录模式:状态的快照与恢复

5.1 从表单草稿箱说起

用户在填写一个复杂的表单时,可能中途想”回退到之前某一步”。备忘录模式可以保存表单在某个时刻的完整状态快照,随时恢复。

备忘录模式的核心思想是:在不暴露对象内部细节的前提下,捕获并保存对象的内部状态,以便以后可以将对象恢复到该状态。

// 备忘录:存储表单快照
class FormSnapshot {
  constructor(
    private readonly data: Record<string, any>,
    public readonly timestamp: Date,
    public readonly label: string
  ) {}

  getData(): Record<string, any> {
    return JSON.parse(JSON.stringify(this.data)); // 返回深拷贝,防止被外部修改
  }
}

// 发起人:复杂表单
class ApplicationForm {
  private fields: Record<string, any> = {};

  setField(key: string, value: any): void {
    this.fields[key] = value;
  }

  getFields(): Record<string, any> {
    return { ...this.fields };
  }

  // 创建快照
  save(label: string): FormSnapshot {
    return new FormSnapshot(
      JSON.parse(JSON.stringify(this.fields)),
      new Date(),
      label
    );
  }

  // 从快照恢复
  restore(snapshot: FormSnapshot): void {
    this.fields = snapshot.getData();
    console.log(`表单已恢复到: ${snapshot.label} (${snapshot.timestamp.toLocaleString()})`);
  }
}

// 管理者:草稿箱
class DraftManager {
  private drafts: FormSnapshot[] = [];
  private maxDrafts: number;

  constructor(maxDrafts = 10) {
    this.maxDrafts = maxDrafts;
  }

  saveDraft(form: ApplicationForm, label: string): void {
    const snapshot = form.save(label);
    this.drafts.push(snapshot);
    if (this.drafts.length > this.maxDrafts) {
      this.drafts.shift(); // 超出限制则丢弃最早的
    }
    console.log(`草稿已保存: ${label}`);
  }

  restoreDraft(form: ApplicationForm, index: number): void {
    if (index < 0 || index >= this.drafts.length) {
      console.log('草稿不存在');
      return;
    }
    form.restore(this.drafts[index]);
  }

  listDrafts(): string[] {
    return this.drafts.map((d, i) => `[${i}] ${d.label} - ${d.timestamp.toLocaleString()}`);
  }
}

注意事项:备忘录模式的主要陷阱是内存开销。每次保存快照都是一次完整的状态复制,如果状态对象很大(比如包含大量嵌套数据或二进制内容),频繁保存会导致内存快速增长。上面的 DraftManager 通过 maxDrafts 限制了快照数量,这是最基本的防护手段。更高级的做法包括:只保存变化的增量(delta)而非完整快照、将旧快照序列化到磁盘或 IndexedDB 中、以及采用写时复制(Copy-on-Write)策略来延迟深拷贝的开销。

使用:

const form = new ApplicationForm();
const drafts = new DraftManager();

form.setField('name', '张三');
form.setField('age', 28);
drafts.saveDraft(form, '填写个人信息');

form.setField('education', '本科');
form.setField('school', '清华大学');
drafts.saveDraft(form, '填写教育经历');

form.setField('school', '北京大学'); // 改错了,想回退

console.log(drafts.listDrafts());
drafts.restoreDraft(form, 0); // 恢复到"填写个人信息"
console.log(form.getFields()); // { name: '张三', age: 28 }

六、访问者模式:为数据结构增加新操作

6.1 从代码分析工具说起

假设你在开发一个 TypeScript AST(抽象语法树)分析工具。AST 的节点类型是相对稳定的(函数声明、变量声明、表达式等),但你需要频繁增加新的分析操作(统计行数、检查命名规范、提取依赖关系等)。

访问者模式的核心思想是:将作用于数据结构中各元素的操作,从元素类中分离出来,封装成独立的”访问者”对象。 适用于数据结构稳定、但操作频繁变化的场景。

// AST 节点接口
interface ASTNode {
  accept(visitor: ASTVisitor): void;
}

// 访问者接口
interface ASTVisitor {
  visitFunction(node: FunctionNode): void;
  visitVariable(node: VariableNode): void;
  visitImport(node: ImportNode): void;
}

// 具体节点
class FunctionNode implements ASTNode {
  constructor(
    public name: string,
    public params: string[],
    public lineCount: number
  ) {}

  accept(visitor: ASTVisitor): void {
    visitor.visitFunction(this);
  }
}

class VariableNode implements ASTNode {
  constructor(
    public name: string,
    public kind: 'const' | 'let' | 'var',
    public hasTypeAnnotation: boolean
  ) {}

  accept(visitor: ASTVisitor): void {
    visitor.visitVariable(this);
  }
}

class ImportNode implements ASTNode {
  constructor(
    public source: string,
    public specifiers: string[]
  ) {}

  accept(visitor: ASTVisitor): void {
    visitor.visitImport(this);
  }
}

// 具体访问者:代码复杂度分析
class ComplexityAnalyzer implements ASTVisitor {
  private longFunctions: string[] = [];
  private totalFunctions = 0;

  visitFunction(node: FunctionNode): void {
    this.totalFunctions++;
    if (node.lineCount > 30) {
      this.longFunctions.push(`${node.name} (${node.lineCount}行)`);
    }
  }

  visitVariable(_node: VariableNode): void { /* 复杂度分析不关心变量 */ }
  visitImport(_node: ImportNode): void { /* 也不关心导入 */ }

  getReport(): string {
    return `函数总数: ${this.totalFunctions},超长函数: ${this.longFunctions.join(', ') || '无'}`;
  }
}

// 具体访问者:命名规范检查
class NamingConventionChecker implements ASTVisitor {
  private violations: string[] = [];

  visitFunction(node: FunctionNode): void {
    if (!/^[a-z][a-zA-Z0-9]*$/.test(node.name)) {
      this.violations.push(`函数 "${node.name}" 应使用 camelCase`);
    }
  }

  visitVariable(node: VariableNode): void {
    const isUpperCaseConst = node.kind === 'const' && node.name === node.name.toUpperCase();
    const isCamelCase = /^[a-z][a-zA-Z0-9]*$/.test(node.name);

    // 允许两种命名:camelCase 变量名,或全大写的 const 常量(如 MAX_RETRY)
    if (!isCamelCase && !isUpperCaseConst) {
      this.violations.push(`变量 "${node.name}" 命名不规范`);
    }
  }

  visitImport(_node: ImportNode): void { /* 导入名由外部库决定,不检查 */ }

  getViolations(): string[] { return this.violations; }
}

使用:

// 模拟 AST
const ast: ASTNode[] = [
  new ImportNode('lodash', ['debounce', 'throttle']),
  new VariableNode('MAX_RETRY', 'const', true),
  new FunctionNode('fetchUserData', ['userId'], 15),
  new FunctionNode('ProcessOrder', ['order'], 45), // 命名违规 + 超长
  new VariableNode('temp_value', 'let', false),      // 命名违规
];

// 运行复杂度分析
const complexity = new ComplexityAnalyzer();
ast.forEach(node => node.accept(complexity));
console.log(complexity.getReport());
// 函数总数: 2,超长函数: ProcessOrder (45行)

// 运行命名检查
const naming = new NamingConventionChecker();
ast.forEach(node => node.accept(naming));
console.log(naming.getViolations());
// ['函数 "ProcessOrder" 应使用 camelCase', '变量 "temp_value" 命名不规范']

🤔 想一想 访问者模式被称为”最复杂的设计模式”之一。在现代 TypeScript 中,使用联合类型 + switch-case 的”标签联合”方案能否替代访问者模式?各有什么优劣?

命令模式和备忘录模式都能实现”撤销”功能,它们的区别是什么?提示:命令记录的是”操作”,备忘录记录的是”状态”。


七、本章小结

本章学习了行为型模式的后六位成员:

模式核心问题一句话总结
责任链请求需要逐级传递和处理把处理者串成链,请求沿链流转
命令操作需要封装、排队、撤销把操作变成对象,支持撤销和重做
状态行为随状态变化而变化每种状态是一个类,委托给当前状态
中介者多个对象之间互相通信太混乱引入中央协调者,解耦直接引用
备忘录需要保存和恢复对象状态拍快照存起来,随时恢复
访问者数据结构稳定但操作频繁变化把操作抽成独立访问者,遍历时调用

至此,GoF 的 23 种经典设计模式中的 22 种已经介绍完毕。还有一种——解释器模式(Interpreter Pattern)——因为在前端开发中使用频率较低,这里做一个简要补充。

补充:解释器模式简介

解释器模式为一种语言定义其语法表示,并提供一个解释器来处理该语法。它的典型应用场景包括:模板引擎解析(如 Mustache 的 {{variable}} 语法)、DSL(领域特定语言)执行、正则表达式引擎、数学表达式计算器等。

在前端领域,你可能不会直接手写一个解释器模式的类层级结构,但你每天都在使用基于它思想的工具——Vue 的模板编译器、Babel 的 AST 转换、ESLint 的规则引擎,底层都涉及”定义语法 → 解析 → 解释执行”的过程。如果你对编译原理或 DSL 设计感兴趣,推荐深入学习这个模式。

下一章我们进入实战篇,看看这些模式在现代前端开发中是如何落地的。


📝 结尾自测

  1. 责任链模式中,处理者之间是什么关系?请求是如何从一个处理者传递到下一个的?
  2. 命令模式实现”撤销”功能的关键是什么?一个命令对象至少需要哪两个方法?
  3. 状态模式和 if-else 状态判断相比,优势在哪里?什么时候用状态模式是过度设计?
  4. 中介者模式解决的核心问题是什么?它有什么潜在的缺点?
  5. 备忘录模式的”三角色”分别是什么?为什么要保证外部不能修改备忘录的内容?

购买课程解锁全部内容

写出优雅代码:10 章掌握 23 种设计模式

¥29.90