命令模式属于行为型设计模式的一种,核心思想是将一个请求封装为一个对象,从而让用户可以用不同的请求对客户进行参数化,同时支持请求的排队、记录日志、撤销等操作。在Golang中实现命令模式,不需要像面向对象语言那样依赖类的继承,通过接口和结构体组合就能轻松完成设计。

命令模式的核心角色
命令模式通常包含以下几个核心角色,每个角色的职责清晰,相互配合完成请求封装的逻辑:
- 命令接口(Command):定义所有命令的统一执行方法,是具体命令的抽象
- 具体命令(ConcreteCommand):实现命令接口,绑定接收者和对应的操作逻辑
- 接收者(Receiver):真正执行请求的对象,包含具体的业务逻辑
- 调用者(Invoker):持有命令对象,负责触发命令的执行
- 客户端(Client):创建具体命令对象,并设置其接收者
Golang实现命令模式的步骤
1. 定义命令接口
首先定义统一的命令接口,所有具体命令都需要实现该接口的Execute方法:
// Command 命令接口,定义执行方法
type Command interface {
Execute() error
}
2. 定义接收者
接收者是实际执行业务逻辑的对象,这里我们以简单的灯控设备为例,定义灯的开关操作:
// Light 接收者,实际执行请求的对象
type Light struct {
Name string
}
// On 开灯操作
func (l *Light) On() error {
fmt.Printf("灯 %s 已打开n", l.Name)
return nil
}
// Off 关灯操作
func (l *Light) Off() error {
fmt.Printf("灯 %s 已关闭n", l.Name)
return nil
}
3. 实现具体命令
具体命令需要绑定对应的接收者,并实现Command接口的Execute方法,将请求转发给接收者的对应方法:
// LightOnCommand 开灯具体命令
type LightOnCommand struct {
light *Light // 绑定的接收者
}
// NewLightOnCommand 创建开灯命令实例
func NewLightOnCommand(light *Light) *LightOnCommand {
return &LightOnCommand{light: light}
}
// Execute 执行开灯命令
func (c *LightOnCommand) Execute() error {
return c.light.On()
}
// LightOffCommand 关灯具体命令
type LightOffCommand struct {
light *Light // 绑定的接收者
}
// NewLightOffCommand 创建关灯命令实例
func NewLightOffCommand(light *Light) *LightOffCommand {
return &LightOffCommand{light: light}
}
// Execute 执行关灯命令
func (c *LightOffCommand) Execute() error {
return c.light.Off()
}
4. 实现调用者
调用者负责持有命令对象,并触发命令的执行,这里模拟一个遥控器作为调用者:
// RemoteControl 调用者,持有命令并触发执行
type RemoteControl struct {
command Command // 当前持有的命令
}
// SetCommand 设置要执行的命令
func (r *RemoteControl) SetCommand(cmd Command) {
r.command = cmd
}
// PressButton 按下按钮触发命令执行
func (r *RemoteControl) PressButton() error {
if r.command == nil {
return fmt.Errorf("未设置命令")
}
return r.command.Execute()
}
5. 客户端组装并使用
客户端负责创建接收者、具体命令,并将命令设置到调用者中,完成整个流程的组装:
package main
import "fmt"
func main() {
// 创建接收者
livingRoomLight := &Light{Name: "客厅灯"}
// 创建具体命令,绑定接收者
lightOnCmd := NewLightOnCommand(livingRoomLight)
lightOffCmd := NewLightOffCommand(livingRoomLight)
// 创建调用者
remote := &RemoteControl{}
// 执行开灯命令
remote.SetCommand(lightOnCmd)
if err := remote.PressButton(); err != nil {
fmt.Printf("执行命令失败: %vn", err)
}
// 执行关灯命令
remote.SetCommand(lightOffCmd)
if err := remote.PressButton(); err != nil {
fmt.Printf("执行命令失败: %vn", err)
}
}
扩展:支持撤销操作的命令模式
命令模式很容易扩展撤销功能,只需要在命令接口中增加Undo方法,具体命令实现对应的撤销逻辑即可:
// CommandWithUndo 支持撤销的命令接口
type CommandWithUndo interface {
Command
Undo() error
}
// LightOnCommand 扩展支持撤销的开灯命令
type LightOnCommand struct {
light *Light
}
func NewLightOnCommand(light *Light) *LightOnCommand {
return &LightOnCommand{light: light}
}
func (c *LightOnCommand) Execute() error {
return c.light.On()
}
// Undo 撤销开灯操作,即执行关灯
func (c *LightOnCommand) Undo() error {
return c.light.Off()
}
// LightOffCommand 扩展支持撤销的关灯命令
type LightOffCommand struct {
light *Light
}
func NewLightOffCommand(light *Light) *LightOffCommand {
return &LightOffCommand{light: light}
}
func (c *LightOffCommand) Execute() error {
return c.light.Off()
}
// Undo 撤销关灯操作,即执行开灯
func (c *LightOffCommand) Undo() error {
return c.light.On()
}
然后在调用者中增加撤销方法的触发逻辑,就可以实现命令的撤销功能,这种方式非常适合需要操作回滚的业务场景。
命令模式的适用场景
在Golang开发中,遇到以下场景时可以考虑使用命令模式:
- 需要将请求调用者和请求接收者解耦,避免直接依赖
- 需要支持请求的排队执行、延迟执行或者定时执行
- 需要记录请求的操作日志,或者支持操作的撤销、重做
- 需要用一个统一的接口封装不同的请求操作,方便参数化管理
命令模式的优势在于降低了系统的耦合度,新增命令时只需要实现命令接口即可,不需要修改调用者和接收者的原有逻辑,符合开闭原则,能够有效提升代码的可维护性和扩展性。