命令模式:操作队列与撤销重做
命令模式:操作队列与撤销重做
命令模式核心原理
命令模式将请求封装为对象,从而支持参数化、队列化、日志化和可撤销操作。每个命令对象包含执行动作和撤销动作,实现了请求的发送者和接收者解耦。在架构中,命令模式是实现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架构中,命令对象是写入侧的核心抽象。建议保持命令对象的不可变性,命令执行后通过事件通知读模型更新。