← 返回首页
🧩

命令模式:操作队列与撤销重做

📂 architecture ⏱ 2 min 349 words

命令模式:操作队列与撤销重做

命令模式核心原理

命令模式将请求封装为对象,从而支持参数化、队列化、日志化和可撤销操作。每个命令对象包含执行动作和撤销动作,实现了请求的发送者和接收者解耦。在架构中,命令模式是实现CQRS、事件溯源和工作流引擎的基础。

// 命令接口
public interface Command {
    void execute();
    void undo();
    String getDescription();
}

// 具体命令 - 转账命令
public class TransferCommand implements Command {
    private final Account fromAccount;
    private final Account toAccount;
    private final BigDecimal amount;
    
    public TransferCommand(Account from, Account to, BigDecimal amount) {
        this.fromAccount = from;
        this.toAccount = to;
        this.amount = amount;
    }
    
    @Override
    public void execute() {
        fromAccount.debit(amount);
        toAccount.credit(amount);
    }
    
    @Override
    public void undo() {
        toAccount.debit(amount);
        fromAccount.credit(amount);
    }
    
    @Override
    public String getDescription() {
        return String.format("Transfer %s from %s to %s", 
            amount, fromAccount.getId(), toAccount.getId());
    }
}

操作队列与历史记录

命令队列管理待执行命令的顺序,支持延迟执行、优先级排序和批量处理。命令历史记录支持完整的操作审计和回滚。

class CommandManager {
  private undoStack: Command[] = [];
  private redoStack: Command[] = [];
  
  execute(command: Command): void {
    command.execute();
    this.undoStack.push(command);
    this.redoStack = []; // 新命令清空重做栈
    this.saveToHistory(command);
  }
  
  undo(): void {
    const command = this.undoStack.pop();
    if (command) {
      command.undo();
      this.redoStack.push(command);
    }
  }
  
  redo(): void {
    const command = this.redoStack.pop();
    if (command) {
      command.execute();
      this.undoStack.push(command);
    }
  }
  
  private async saveToHistory(command: Command): Promise<void> {
    await db.commandHistory.create({
      description: command.getDescription(),
      executedAt: new Date(),
      commandType: command.constructor.name,
    });
  }
}

// 宏命令 - 组合多个命令
class MacroCommand implements Command {
  private commands: Command[];
  
  constructor(commands: Command[]) {
    this.commands = commands;
  }
  
  execute(): void {
    this.commands.forEach(cmd => cmd.execute());
  }
  
  undo(): void {
    [...this.commands].reverse().forEach(cmd => cmd.undo());
  }
  
  getDescription(): string {
    return `Macro: ${this.commands.map(c => c.getDescription()).join(', ')}`;
  }
}

事务日志与重放

命令模式与事务日志结合,实现操作的持久化存储和失败重放。所有命令序列化到日志存储,系统重启后可按序重放。

// 命令日志存储
type CommandLog struct {
    ID        string
    Command   string
    Args      []byte
    Timestamp time.Time
    Status    string
}

type CommandLogStore struct {
    db *sql.DB
}

func (s *CommandLogStore) Append(cmd Command) error {
    serialized, err := json.Marshal(cmd)
    if err != nil {
        return err
    }
    
    _, err = s.db.Exec(
        "INSERT INTO command_log (id, command_type, args, timestamp, status) VALUES (?, ?, ?, ?, ?)",
        uuid.New().String(), cmd.GetType(), serialized, time.Now(), "EXECUTED",
    )
    return err
}

// 重放未完成的命令
func (s *CommandLogStore) ReplayPending() ([]Command, error) {
    rows, err := s.db.Query("SELECT * FROM command_log WHERE status = 'PENDING' ORDER BY timestamp")
    if err != nil {
        return nil, err
    }
    defer rows.Close()
    
    var commands []Command
    for rows.Next() {
        var logEntry CommandLog
        rows.Scan(&logEntry.ID, &logEntry.Command, &logEntry.Args, &logEntry.Timestamp, &logEntry.Status)
        
        cmd, err := deserializeCommand(logEntry.Command, logEntry.Args)
        if err != nil {
            continue
        }
        commands = append(commands, cmd)
    }
    return commands, nil
}

命令模式最佳实践

命令模式通过封装请求实现操作的序列化、持久化和撤销。在CQRS架构中,命令对象是写入侧的核心抽象。建议保持命令对象的不可变性,命令执行后通过事件通知读模型更新。