变量命令模式封装是将变量操作封装为可存、可传、可撤回的对象,核心角色包括receiver(只管基础操作)、command(封装变更与快照)和invoker(统一调度执行),通过执行前捕获确定快照实现可靠undo,并支持compositecommand批量联动与回滚。

变量的命令模式封装,核心是把“对某个变量的读、写、切换、计算等操作”变成可存、可传、可撤回的对象,而不是散落在各处的赋值语句或函数调用。它不为炫技,而是当变量状态需要被追踪、回滚、批量执行或权限控制时,提供一种清晰可控的抽象方式。
明确谁改变量、怎么改、何时改
先划清三类角色边界,避免把逻辑混在一起:
-
Receiver(变量持有者):只管变量本身和基础操作,比如一个
User结构体,暴露SetName()、SetAge()、GetStateSnapshot(),但不记录日志、不判断能否修改、不保存历史 -
Command(命令对象):封装“改什么+怎么改+改前快照”,例如
ChangeUserNameCommand持有*User引用和旧名字字段;execute()调用user.SetName(new),undo()调用user.SetName(old) -
Invoker(执行调度者):比如一个
VariableEditor,只接收Command接口,调用cmd.Execute(),完全不知道背后是改名字还是调API
让变量操作真正可撤销,关键在快照时机
撤销不是靠“猜变量原来是什么”,而是在execute()之前就捕获确定的状态:
- 简单类型(string/int/bool):直接拷贝值,如
oldName := user.Name - 结构体或嵌套数据:用值拷贝(
u := *user)或深拷贝(json.Marshal/Unmarshal),避免引用导致Undo时操作的是最新值 - 不要在
undo()里重新查数据库或读缓存——那不是还原,是重查;快照必须在命令创建或execute()入口处完成
支持组合与批量,用CompositeCommand统一管理
当多个变量要联动修改(如注册时同时设用户名、邮箱、默认头像),别写一堆独立命令再手动调用:
- 定义
CompositeCommand,内部持有一个[]Command切片 -
Execute()顺序执行每个子命令;任意失败则全部回滚(从后往前调undo()) - 它自己也实现
Command接口,可被上层Invoker当作单个命令使用,也可压入历史栈统一撤销
Go语言中轻量实现的实用写法
不必强套Java式接口,用结构体+两个闭包更自然:
type VarCommand struct {
Do func()
Undo func()
Name string // 仅用于日志或调试,非必需
}
// 创建时立刻捕获上下文
cmd := VarCommand{
Do: func() { user.Role = "admin" },
Undo: func() { user.Role = "user" }, // 注意:这里必须是执行时的旧值,不是当前值
Name: "set-role-to-admin",
}
若涉及复杂状态,建议封装成具名结构体,而非裸闭包,便于测试与扩展。










