命令模式将请求封装为对象,实现发送者与接收者解耦,支持撤销、重做、异步任务管理。通过Command接口和具体实现(如TurnOnLightCommand),结合调用者(Invoker)与历史记录栈,可统一调度操作,提升系统灵活性与可维护性。

Golang中的命令模式,说白了,就是把一个请求封装成一个对象。这样一来,请求的发送者和接收者就彻底解耦了。我个人觉得,它最核心的价值在于,它能让你把“做什么”和“怎么做”分离开来,这在很多复杂系统里简直是福音。你可以把不同的操作当成一个个独立的命令对象,然后统一管理、调度,甚至还能玩出撤销、重做、日志记录这些花样。
在Golang里实践命令模式,我们通常会定义一个接口,比如
Command
Execute()
Command
想象一下,你有一个智能家居系统。你不想让遥控器(Invoker)直接知道怎么操作灯泡(Receiver),它只需要知道“我有一个打开灯的命令”就行了。
定义命令接口:
立即学习“go语言免费学习笔记(深入)”;
package main
import "fmt"
// Command 是命令接口,所有具体命令都应该实现它
type Command interface {
Execute() error
}定义接收者(Receiver): 这是真正执行操作的对象。比如,一个灯泡。
// Light 是一个接收者,知道如何打开和关闭
type Light struct {
Name string
isOn bool
}
func (l *Light) TurnOn() {
if !l.isOn {
fmt.Printf("%s 灯亮了\n", l.Name)
l.isOn = true
} else {
fmt.Printf("%s 灯已经亮着呢\n", l.Name)
}
}
func (l *Light) TurnOff() {
if l.isOn {
fmt.Printf("%s 灯灭了\n", l.Name)
l.isOn = false
} else {
fmt.Printf("%s 灯已经灭着呢\n", l.Name)
}
}定义具体命令(Concrete Command): 这些命令会持有接收者的引用,并调用接收者的特定方法。
// TurnOnLightCommand 是打开灯的命令
type TurnOnLightCommand struct {
light *Light
}
func (c *TurnOnLightCommand) Execute() error {
c.light.TurnOn()
return nil
}
// TurnOffLightCommand 是关闭灯的命令
type TurnOffLightCommand struct {
light *Light
}
func (c *TurnOffLightCommand) Execute() error {
c.light.TurnOff()
return nil
}定义调用者(Invoker): 调用者不关心具体命令的实现细节,只知道如何执行一个命令。
// RemoteControl 是调用者,它持有并执行命令
type RemoteControl struct {
command Command
}
func (rc *RemoteControl) SetCommand(cmd Command) {
rc.command = cmd
}
func (rc *RemoteControl) PressButton() error {
if rc.command == nil {
return fmt.Errorf("没有设置命令")
}
fmt.Println("遥控器按钮被按下...")
return rc.command.Execute()
}实际使用:
// main 函数,模拟客户端代码
func main() {
livingRoomLight := &Light{Name: "客厅"}
bedroomLight := &Light{Name: "卧室"}
turnOnLivingRoom := &TurnOnLightCommand{light: livingRoomLight}
turnOffBedroom := &TurnOffLightCommand{light: bedroomLight}
turnOnBedroom := &TurnOnLightCommand{light: bedroomLight}
remote := &RemoteControl{}
// 打开客厅灯
remote.SetCommand(turnOnLivingRoom)
remote.PressButton()
// 关闭卧室灯
remote.SetCommand(turnOffBedroom)
remote.PressButton()
// 再次打开卧室灯
remote.SetCommand(turnOnBedroom)
remote.PressButton()
// 尝试关闭客厅灯
remote.SetCommand(&TurnOffLightCommand{light: livingRoomLight})
remote.PressButton()
}通过这种方式,
RemoteControl
Command
Execute
这个问题挺有意思的,说实话,Golang本身对函数式编程的支持不像Haskell或Scala那么纯粹,但它的一等公民函数特性,确实让命令模式有了新的玩法。在我看来,结合点主要体现在,你可以用一个函数类型来充当简单的命令接口,或者在命令结构体内部封装一个函数。
比如,我们最初的
Command
interface { Execute() error }type Action func() error
什么时候用函数,什么时候用结构体呢? 如果你的命令非常简单,不需要维护任何状态,或者所有状态都能通过闭包捕获,那么直接使用
func() error
// 简单的函数式命令
type SimpleCommand func() error
func (s SimpleCommand) Execute() error {
return s()
}
func main() {
// ... (上面的Light和RemoteControl定义)
// 使用函数式命令来打印一条消息
logCommand := SimpleCommand(func() error {
fmt.Println("这是一个日志命令,由函数实现。")
return nil
})
remote := &RemoteControl{}
remote.SetCommand(logCommand)
remote.PressButton()
// 甚至可以直接把一个匿名函数赋值给一个变量,然后作为命令执行
// 但如果想塞到RemoteControl里,还是需要一个统一的接口。
// 这时候,SimpleCommand这个适配器就派上用场了。
}但如果你的命令需要:
Execute
这种情况下,一个结构体实现的命令模式会更合适。结构体能更好地封装状态和行为。
当然,你也可以玩一个“混合体”:一个命令结构体内部包含一个函数字段。这样,命令结构体负责状态和上下文,而具体的执行逻辑则委托给内部的函数。这在一些需要高度灵活性的场景下,比如动态生成命令逻辑时,会非常有用。
撤销和重做是命令模式最经典的用例之一。它能实现的关键,就在于命令被封装成了对象,并且这些对象知道如何“反向操作”。
要实现撤销,我们的
Command
Undo()
// UndoableCommand 是支持撤销的命令接口
type UndoableCommand interface {
Execute() error
Undo() error
}接着,我们修改具体的命令,让它们也实现
Undo()
TurnOnLightCommand
TurnOff()
TurnOffLightCommand
TurnOn()
// TurnOnLightCommand 变为可撤销的
type TurnOnLightCommand struct {
light *Light
}
func (c *TurnOnLightCommand) Execute() error {
c.light.TurnOn()
return nil
}
func (c *TurnOnLightCommand) Undo() error {
c.light.TurnOff() // 打开的命令,撤销就是关闭
return nil
}
// TurnOffLightCommand 变为可撤销的
type TurnOffLightCommand struct {
light *Light
}
func (c *TurnOffLightCommand) Execute() error {
c.light.TurnOff()
return nil
}
func (c *TurnOffLightCommand) Undo() error {
c.light.TurnOn() // 关闭的命令,撤销就是打开
return nil
}然后,我们需要一个“历史记录”机制来存储执行过的命令,以便将来撤销。通常,我们会用两个栈(或者切片模拟栈)来实现:
undoStack
redoStack
当一个命令被
Execute()
undoStack
redoStack
Undo()
undoStack
Undo()
redoStack
Redo()
redoStack
Execute()
undoStack
// CommandHistory 管理命令历史,支持撤销和重做
type CommandHistory struct {
undoStack []UndoableCommand
redoStack []UndoableCommand
}
func NewCommandHistory() *CommandHistory {
return &CommandHistory{
undoStack: make([]UndoableCommand, 0),
redoStack: make([]UndoableCommand, 0),
}
}
func (ch *CommandHistory) ExecuteAndRecord(cmd UndoableCommand) error {
err := cmd.Execute()
if err != nil {
return err
}
ch.undoStack = append(ch.undoStack, cmd)
ch.redoStack = make([]UndoableCommand, 0) // 新操作会清空重做历史
fmt.Println("命令已执行并记录。")
return nil
}
func (ch *CommandHistory) Undo() error {
if len(ch.undoStack) == 0 {
return fmt.Errorf("没有可撤销的命令")
}
cmd := ch.undoStack[len(ch.undoStack)-1]
ch.undoStack = ch.undoStack[:len(ch.undoStack)-1]
err := cmd.Undo()
if err != nil {
return err
}
ch.redoStack = append(ch.redoStack, cmd)
fmt.Println("命令已撤销。")
return nil
}
func (ch *CommandHistory) Redo() error {
if len(ch.redoStack) == 0 {
return fmt.Errorf("没有可重做的命令")
}
cmd := ch.redoStack[len(ch.redoStack)-1]
ch.redoStack = ch.redoStack[:len(ch.redoStack)-1]
err := cmd.Execute()
if err != nil {
return err
}
ch.undoStack = append(ch.undoStack, cmd)
fmt.Println("命令已重做。")
return nil
}
func main() {
// ... (Light定义)
livingRoomLight := &Light{Name: "客厅"}
bedroomLight := &Light{Name: "卧室"}
history := NewCommandHistory()
// 执行并记录命令
history.ExecuteAndRecord(&TurnOnLightCommand{light: livingRoomLight})
history.ExecuteAndRecord(&TurnOnLightCommand{light: bedroomLight})
history.ExecuteAndRecord(&TurnOffLightCommand{light: livingRoomLight})
fmt.Println("\n--- 尝试撤销 ---")
history.Undo() // 撤销关闭客厅灯
history.Undo() // 撤销打开卧室灯
fmt.Println("\n--- 尝试重做 ---")
history.Redo() // 重做打开卧室灯
history.Redo() // 重做关闭客厅灯
fmt.Println("\n--- 再次执行新命令,重做历史被清空 ---")
history.ExecuteAndRecord(&TurnOnLightCommand{light: livingRoomLight})
history.Redo() // 此时会报错,因为重做栈已空
}实现撤销/重做时,最大的挑战是确保
Undo()
Execute()
Undo()
Execute()
在Golang中处理异步请求,
goroutine
channel
任务抽象与解耦: 一个异步请求,比如发送一个HTTP请求、执行一个数据库查询,本身就是一个操作。用命令模式,你可以把这个操作封装成一个
AsyncCommand
AsyncCommand
统一的异步任务队列: 你可以创建一个
WorkerPool
TaskQueue
AsyncCommand
WorkerPool
goroutine
// AsyncCommand 异步命令接口,可能需要返回一个结果或错误通道
type AsyncCommand interface {
ExecuteAsync() chan error // 或者 chan interface{} 来返回结果
}
// HTTPRequestCommand 封装一个异步HTTP请求
type HTTPRequestCommand struct {
URL string
Method string
Body []byte
Response chan []byte // 用于返回响应
Error chan error // 用于返回错误
}
func (c *HTTPRequestCommand) ExecuteAsync() chan error {
errChan := make(chan error, 1)
go func() {
// 模拟一个耗时的HTTP请求
fmt.Printf("异步执行 HTTP %s 请求到 %s...\n", c.Method, c.URL)
time.Sleep(time.Second * 2) // 模拟网络延迟
if c.URL == "http://bad.example.com" {
errChan <- fmt.Errorf("请求 %s 失败:网络错误", c.URL)
return
}
// 模拟成功响应
c.Response <- []byte(fmt.Sprintf("成功响应来自 %s", c.URL))
errChan <- nil
}()
return errChan
}
// WorkerPool 异步命令执行池
type WorkerPool struct {
commandQueue chan AsyncCommand
workerCount int
}
func NewWorkerPool(workers int) *WorkerPool {
return &WorkerPool{
commandQueue: make(chan AsyncCommand, workers*2), // 缓冲区
workerCount: workers,
}
}
func (wp *WorkerPool) Start() {
for i := 0; i < wp.workerCount; i++ {
go wp.worker(i)
}
}
func (wp *WorkerPool) worker(id int) {
fmt.Printf("工作者 %d 启动...\n", id)
for cmd := range wp.commandQueue {
errChan := cmd.ExecuteAsync()
err := <-errChan
if err != nil {
fmt.Printf("工作者 %d 执行命令失败: %v\n", id, err)
} else {
// 通常这里会从 cmd.Response 接收结果
fmt.Printf("工作者 %d 执行命令成功。\n", id)
}
}
}
func (wp *WorkerPool) Submit(cmd AsyncCommand) {
wp.commandQueue <- cmd
}
func (wp *WorkerPool) Stop() {
close(wp.commandQueue)
}
func main() {
pool := NewWorkerPool(3)
pool.Start()
resp1 := make(chan []byte, 1)
err1 := make(chan error, 1)
req1 := &HTTPRequestCommand{URL: "http://example.com/api/data", Method: "GET", Response: resp1, Error: err1}
pool.Submit(req1)
resp2 := make(chan []byte, 1)
err2 := make(chan error, 1)
req2 := &HTTPRequestCommand{URL: "http://bad.example.com", Method: "POST", Response: resp2, Error: err2}
pool.Submit(req2)
// 等待结果
select {
case res := <-resp1:
fmt.Printf("收到响应: %s\n", string(res))
case err := <-err1:
fmt.Printf("请求出错: %v\n", err)
case <-time.After(5 * time.Second):
fmt.Println("等待请求1超时")
}
select {
case res := <-resp2:
fmt.Printf("收到响应: %s\n", string(res))
case err := <-err2:
fmt.Printf("请求出错: %v\n", err)
case <-time.After(5 * time.Second):
fmt.Println("等待请求2超时")
}
time.Sleep(time.Second * 3) // 等待所有worker完成
pool.Stop()
}以上就是Golang命令模式封装请求与执行实践的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号