15. 命令模式(Command)

问题:想把「操作」封装成对象,支持撤销、队列、日志等。

核心:每个操作是一个命令对象,包含执行和撤销方法。

interface Command {
  execute(): void;
  unexecute(): void;
}

class Light {
  #state: boolean = false;
  on(): void  { this.#state = true;  console.log('灯开了'); }
  off(): void { this.#state = false; console.log('灯关了'); }
}

class LightOnCommand implements Command {
  #light: Light;
  constructor(light: Light) { this.#light = light; }
  execute(): void    { this.#light.on(); }
  unexecute(): void  { this.#light.off(); }
}

class RemoteControl {
  #history: Command[] = [];
  execute(command: Command): void {
    command.execute();
    this.#history.push(command);
  }
  undo(): void {
    this.#history.pop()?.unexecute();
  }
}

const remote = new RemoteControl();
const light = new Light();
remote.execute(new LightOnCommand(light)); // 灯开了
remote.undo();                             // 灯关了
package command

import "fmt"

type Command interface {
  Execute()
  Unexecute()
}

type Light struct{ state bool }

func (l *Light) On()  { l.state = true;  fmt.Println("灯开了") }
func (l *Light) Off() { l.state = false; fmt.Println("灯关了") }

type LightOnCommand struct{ light *Light }

func (c LightOnCommand) Execute()   { c.light.On() }
func (c LightOnCommand) Unexecute() { c.light.Off() }

type RemoteControl struct {
  history []Command
}

func (r *RemoteControl) Execute(cmd Command) {
  cmd.Execute()
  r.history = append(r.history, cmd)
}

func (r *RemoteControl) Undo() {
  if len(r.history) == 0 {
    return
  }
  cmd := r.history[len(r.history)-1]
  r.history = r.history[:len(r.history)-1]
  cmd.Unexecute()
}

实战场景

  • 撤销和重做:编辑器里的输入、删除、移动、格式化都封装成命令,执行后入栈,撤销时反向执行。
  • 操作队列:批量导入、发送邮件、生成报表等耗时任务可以排成命令队列异步执行。
  • 宏命令:把一组操作组合成一个命令,例如“格式化文档”内部包含缩进、排序、修复空行。
  • 快捷键绑定:键盘快捷键只绑定命令对象,不直接绑定具体业务函数,便于重映射。
  • 菜单和工具栏:菜单项、按钮、右键操作都触发同一个命令,避免多个入口复制逻辑。
  • 任务调度:定时任务、延迟任务、重试任务都可以保存为命令并由 scheduler 执行。
  • 审计日志:命令天然记录了“谁在什么时候请求了什么操作”,适合关键业务留痕。
  • 远程执行:客户端把命令序列化发到服务端执行,例如协作编辑、远程控制、自动化脚本。
  • 事务脚本:一组命令要么全部成功,要么失败后补偿,常用于订单、库存、支付流程。
  • 测试自动化:UI 测试或端到端脚本可以表示成命令列表,便于复用和回放。