命令模式:把操作变成"快递包裹"
想象你在餐厅点餐:你不会直接冲进厨房告诉厨师"给我做个汉堡",而是把订单写在纸上,服务员会在合适的时机把它交给厨房。订单就像一个"命令包裹"——封装了你的请求,可以排队、可以取消、甚至可以追溯谁点了什么。
命令模式 将请求封装成独立的对象,让你能够参数化、排队、记录日志,甚至支持撤销操作。
为什么需要命令模式?
假设你在开发一个智能家居系统,需要支持多种设备的控制:
// ❌ 糟糕的写法:直接调用设备方法
func controlDevice(device string, action string) {
switch device {
case "tv":
if action == "on" {
tv.TurnOn()
} else {
tv.TurnOff()
}
case "light":
if action == "on" {
light.TurnOn()
}
// ... 无穷无尽的 switch-case
}
}
这样写的问题:
- 控制逻辑和设备紧耦合 :新增设备要改核心代码
- 无法撤销 :关掉电视后想再打开?重写逻辑
- 无法排队 :想定时执行一系列操作?做不到
命令模式的解法: 把每个操作封装成独立的命令对象,让调用者只管"发命令",不管具体怎么执行。
模式结构
| 角色 | 职责 | 类比 |
|---|---|---|
| Invoker(调用者) | 发起请求,但不知道谁来执行 | 遥控器按钮 |
| Command(命令接口) | 定义执行命令的统一接口 | 所有按钮的"按下"动作 |
| ConcreteCommand(具体命令) | 封装具体操作和接收者 | "开机"按钮、"关机"按钮 |
| Receiver(接收者) | 实际执行业务逻辑的对象 | 电视机本身 |
| Client(客户端) | 创建命令并配置调用者 | 组装遥控器的人 |
动手实现:电视遥控器
用一个电视遥控器的例子来演示命令模式。遥控器可以控制多种设备的开关。
第一步:定义接收者(设备)
第二步:定义命令接口和具体命令
第三步:定义调用者(遥控器按钮)
第四步:组装并使用
package main
func main() {
// 1. 创建接收者(电视机)
tv := &TV{}
// 2. 创建命令对象,绑定到接收者
onCommand := &OnCommand{device: tv}
offCommand := &OffCommand{device: tv}
// 3. 创建调用者(按钮),绑定命令
onButton := &Button{command: onCommand}
offButton := &Button{command: offCommand}
// 4. 用户操作:按下按钮
fmt.Println("用户按下「开机」按钮...")
onButton.Press()
fmt.Println("\n用户按下「关机」按钮...")
offButton.Press()
}
进阶用法:命令队列与撤销
命令模式的威力远不止于此,它还能实现:
命令队列
// CommandQueue 命令队列,支持批量执行
type CommandQueue struct {
commands []Command
}
func (q *CommandQueue) Add(cmd Command) {
q.commands = append(q.commands, cmd)
}
func (q *CommandQueue) ExecuteAll() {
for _, cmd := range q.commands {
cmd.Execute()
}
}
撤销功能
// UndoableCommand 可撤销的命令接口
type UndoableCommand interface {
Command
Undo()
}
// OnCommand 实现撤销
func (c *OnCommand) Undo() {
c.device.Off() // 撤销开机 = 关机
}
什么时候该用命令模式?
| 场景 | 说明 |
|---|---|
| 需要参数化操作 | 把操作当作参数传递给方法 |
| 需要排队或延迟执行 | 任务队列、定时任务、宏命令 |
| 需要撤销/重做 | 文本编辑器、绘图软件、游戏存档 |
| 需要记录操作日志 | 事务系统、审计日志 |
常见应用 :
- GUI 按钮和菜单项:同一个命令可以绑定到按钮、快捷键、菜单
- 事务系统:数据库事务的回滚机制
- 宏录制:Office 宏、游戏宏
- 任务调度:Cron Job、消息队列
优缺点分析
| ✅ 优点 | ❌ 缺点 |
|---|---|
| 解耦调用者和接收者:遥控器不需要知道电视的内部实现 | 类数量增加:每种操作都要一个命令类 |
| 支持撤销/重做:命令对象可以保存状态 | 简单场景过度设计:直接调用更简单 |
| 支持组合命令:宏命令可以包含多个子命令 | |
| 支持延迟执行:命令可以放入队列稍后执行 |
与其他模式的关系
| 模式组合 | 说明 |
|---|---|
| 命令 + 备忘录 | 实现撤销功能:命令执行前用备忘录保存状态 |
| 命令 + 责任链 | 命令在处理者链中传递,直到被执行 |
| 命令 vs 策略 | 策略是"怎么做",命令是"做什么" |
一句话总结:命令模式就像快递系统——你把包裹(请求)交给快递员(调用者),快递员不关心里面是什么,只负责送到收件人(接收者)手里。
