首页 > 后端开发 > Golang > 正文

Golang命令模式封装请求与执行实践

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

golang命令模式封装请求与执行实践

Golang中的命令模式,说白了,就是把一个请求封装成一个对象。这样一来,请求的发送者和接收者就彻底解耦了。我个人觉得,它最核心的价值在于,它能让你把“做什么”和“怎么做”分离开来,这在很多复杂系统里简直是福音。你可以把不同的操作当成一个个独立的命令对象,然后统一管理、调度,甚至还能玩出撤销、重做、日志记录这些花样。

解决方案

在Golang里实践命令模式,我们通常会定义一个接口,比如

Command
登录后复制
,它只包含一个
Execute()
登录后复制
方法。所有具体的请求,比如“打开灯”、“关闭门”,都会被封装成实现这个
Command
登录后复制
接口的结构体。这些结构体内部会持有真正执行操作的对象(我们称之为接收者,Receiver)的引用,并包含执行操作所需的参数。

想象一下,你有一个智能家居系统。你不想让遥控器(Invoker)直接知道怎么操作灯泡(Receiver),它只需要知道“我有一个打开灯的命令”就行了。

  1. 定义命令接口:

    立即学习go语言免费学习笔记(深入)”;

    package main
    
    import "fmt"
    
    // Command 是命令接口,所有具体命令都应该实现它
    type Command interface {
        Execute() error
    }
    登录后复制
  2. 定义接收者(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)
        }
    }
    登录后复制
  3. 定义具体命令(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
    }
    登录后复制
  4. 定义调用者(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()
    }
    登录后复制
  5. 实际使用:

    // 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中,命令模式与函数式编程的结合点在哪里?

这个问题挺有意思的,说实话,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这个适配器就派上用场了。
}
登录后复制

但如果你的命令需要:

AI封面生成器
AI封面生成器

专业的AI封面生成工具,支持小红书、公众号、小说、红包、视频封面等多种类型,一键生成高质量封面图片。

AI封面生成器 108
查看详情 AI封面生成器
  • 维护状态: 比如一个“调整亮度”的命令,它可能需要记住当前亮度值,或者调整的步长。
  • 复杂的生命周期或资源管理: 命令执行前后可能需要初始化或清理资源。
  • 实现多个操作(如撤销): 这就要求命令对象有多个方法,而不仅仅是
    Execute
    登录后复制
  • 需要通过反射或类型断言进行识别: 命令对象本身携带类型信息。

这种情况下,一个结构体实现的命令模式会更合适。结构体能更好地封装状态和行为。

当然,你也可以玩一个“混合体”:一个命令结构体内部包含一个函数字段。这样,命令结构体负责状态和上下文,而具体的执行逻辑则委托给内部的函数。这在一些需要高度灵活性的场景下,比如动态生成命令逻辑时,会非常有用。

如何在Golang中实现命令的撤销(Undo)与重做(Redo)功能?

撤销和重做是命令模式最经典的用例之一。它能实现的关键,就在于命令被封装成了对象,并且这些对象知道如何“反向操作”。

要实现撤销,我们的

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命令模式的优势体现在哪些方面?

在Golang中处理异步请求,

goroutine
登录后复制
channel
登录后复制
几乎是标配。但当异步请求变得复杂,需要统一管理、排队、限流、错误重试或者状态追踪时,命令模式就能派上大用场了。

  1. 任务抽象与解耦: 一个异步请求,比如发送一个HTTP请求、执行一个数据库查询,本身就是一个操作。用命令模式,你可以把这个操作封装成一个

    AsyncCommand
    登录后复制
    对象。这个对象包含了请求的所有细节(URL、参数、回调函数等),而发起请求的组件(调用者)只知道它要执行一个
    AsyncCommand
    登录后复制
    ,而无需关心这个请求是如何被发送、如何处理响应的。这极大地提高了代码的模块化和可维护性。

  2. 统一的异步任务队列: 你可以创建一个

    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中文网其它相关文章!

最佳 Windows 性能的顶级免费优化软件
最佳 Windows 性能的顶级免费优化软件

每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。

下载
来源:php中文网
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
开源免费商场系统广告
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新 English
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习

Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号