当前位置: 首页 > news >正文

Go语言设计模式:命令模式详解

文章目录

    • 一、命令模式概述
      • 1.1 什么是命令模式?
      • 1.2 为什么需要命令模式?(解决的问题)
      • 1.3 命令模式的结构
      • 1.4 优缺点分析
      • 1.5 适用场景
    • 二、Go语言实现:智能家居遥控器
      • 2.1 步骤 1: 定义 Command 接口
      • 2.2 步骤 2: 创建 Receiver
      • 2.3 步骤 3: 创建 ConcreteCommand
      • 2.4 步骤 4: 创建 Invoker
      • 2.5 步骤 5: 客户端代码
      • 2.6 完整代码
      • 2.7 执行结果

一、命令模式概述

1.1 什么是命令模式?

命令模式(Command Pattern)是一种行为型设计模式,它将一个请求封装为一个对象,从而允许你用不同的请求对客户进行参数化、对请求排队或记录请求日志,以及支持可撤销的操作。
简单来说,命令模式就是将“发起请求的对象”与“执行请求的对象”解耦。它把一个请求(比如“打开灯”)的所有相关信息(接收者、操作、参数)都打包到一个独立的“命令对象”中。

一个绝佳的比喻: 想象一下在餐厅吃饭。

  • 你(客户端 Client):想点一份牛排。
  • 服务员(调用者 Invoker):负责接收你的订单。
  • 订单(命令 Command):一张写明了“做一份七分熟牛排”的纸条。这个纸条就是请求的封装。
  • 厨师(接收者 Receiver):真正知道如何做牛排的人。

这个过程是:你(Client)告诉服务员(Invoker)要点牛排,服务员创建了一张“做牛排”的订单(Command),然后把订单交给厨师(Receiver)去执行。在这个过程中,你(Client)和厨师(Receiver)完全没有直接接触。服务员甚至可以把订单先放着,或者一次性交给厨师多个订单(排队)。

1.2 为什么需要命令模式?(解决的问题)

命令模式主要解决以下问题:

  1. 解耦:将请求的发送者和接收者完全解耦。发送者只需要知道如何发送命令,而无需知道命令是如何被执行的,以及由谁执行。
  2. 可扩展性:可以很容易地增加新的命令。只需要创建一个新的命令类,实现命令接口即可,无需修改现有代码。
  3. 支持撤销/重做:由于命令被封装成了对象,我们可以为命令对象实现 undo() 方法。通过保存执行过的命令历史,就可以轻松实现撤销和重做功能。
  4. 支持队列和日志:命令对象可以被放入队列中,用于实现异步操作、任务调度等。也可以将命令对象序列化,用于记录操作日志,以便在系统崩溃后恢复。

1.3 命令模式的结构

  1. Command (命令接口):声明执行操作的接口,通常只有一个 Execute() 方法,可能还会有 Undo() 方法。
  2. ConcreteCommand (具体命令):实现了命令接口。它持有一个对接收者对象的引用,并负责调用接收者的相应方法来完成请求。
  3. Receiver (接收者):知道如何执行与请求相关的操作。任何类都可能成为接收者。
  4. Invoker (调用者):负责调用命令。它持有一个命令对象,并在某个时间点调用命令的 Execute() 方法。
  5. Client (客户端):创建具体命令对象,并设置它的接收者。然后创建调用者,并将命令对象交给调用者。

UML 结构图:

+----------------+      +-----------------+
|     Client     |----->|     Invoker     |
|----------------|      |-----------------|
|                |      | - command       |
|                |      |-----------------|
|                |      | + SetCommand()  |
|                |      | + ExecuteCommand()|
+----------------+      +-----------------+|                       || creates               | usesv                       v
+----------------+      +-----------------+
|    Command     |<-----| ConcreteCommand |
|----------------|      |-----------------|
| + Execute()    |      | - receiver      |
| + Undo()       |      |-----------------|
+----------------+      | + Execute()     |^               | + Undo()        ||               +-----------------+| implements|
+-----------------+
|    Receiver     |
|-----------------|
| + Action()      |
| + UnAction()    |
+-----------------+

1.4 优缺点分析

优点:

  1. 解耦:彻底解耦了调用者和接收者。
  2. 扩展性强:增加新命令非常容易,符合开闭原则。
  3. 支持复合命令:可以将多个命令组合成一个“宏命令”,通过一个命令执行一系列操作。
  4. 支持撤销/重做:为系统增加了强大的功能,且实现相对简单。

缺点:

  1. 类爆炸:每一个具体操作都需要一个具体的命令类,这可能会导致系统中产生大量的命令类,增加维护成本。

1.5 适用场景

  • 需要将请求调用者和请求接收者解耦,使得调用者不需要知道接收者的任何信息。
  • 需要在不同的时间指定请求、将请求排队和执行请求。
  • 需要支持撤销/重做操作。
  • 需要支持事务操作,即一组操作要么全部成功,要么全部失败。
  • GUI 中的菜单项、按钮、快捷键等,它们都是命令模式的典型应用。

二、Go语言实现:智能家居遥控器

场景:我们有一个智能遥控器,它可以控制不同的家电(如电灯、音响)。我们希望可以按下按钮来执行命令,并且支持撤销操作。

2.1 步骤 1: 定义 Command 接口

// Command 命令接口
type Command interface {Execute()Undo()
}

2.2 步骤 2: 创建 Receiver

// Receiver 1: 电灯
type Light struct {isOn bool
}
func (l *Light) On() {l.isOn = truefmt.Println("电灯已打开")
}
func (l *Light) Off() {l.isOn = falsefmt.Println("电灯已关闭")
}
// Receiver 2: 音响
type Stereo struct {isOn boolvolume int
}
func (s *Stereo) On() {s.isOn = truefmt.Println("音响已打开")
}
func (s *Stereo) Off() {s.isOn = falsefmt.Println("音响已关闭")
}
func (s *Stereo) SetVolume(volume int) {s.volume = volumefmt.Printf("音响音量设置为 %d\n", volume)
}

2.3 步骤 3: 创建 ConcreteCommand

// ConcreteCommand 1: 打开电灯的命令
type LightOnCommand struct {light *Light
}
func NewLightOnCommand(light *Light) *LightOnCommand {return &LightOnCommand{light: light}
}
func (c *LightOnCommand) Execute() {c.light.On()
}
func (c *LightOnCommand) Undo() {c.light.Off()
}
// ConcreteCommand 2: 关闭电灯的命令
type LightOffCommand struct {light *Light
}
func NewLightOffCommand(light *Light) *LightOffCommand {return &LightOffCommand{light: light}
}
func (c *LightOffCommand) Execute() {c.light.Off()
}
func (c *LightOffCommand) Undo() {c.light.On()
}
// ConcreteCommand 3: 打开音响并设置音量的命令
type StereoOnWithCDCommand struct {stereo *StereoprevVolume int // 用于撤销
}
func NewStereoOnWithCDCommand(stereo *Stereo) *StereoOnWithCDCommand {return &StereoOnWithCDCommand{stereo: stereo}
}
func (c *StereoOnWithCDCommand) Execute() {c.prevVolume = c.stereo.volume // 保存之前的状态c.stereo.On()c.stereo.SetVolume(11)
}
func (c *StereoOnWithCDCommand) Undo() {c.stereo.SetVolume(c.prevVolume) // 恢复之前的状态c.stereo.Off()
}
// ConcreteCommand 4: 空命令,用于初始化
type NoCommand struct{}
func (c *NoCommand) Execute() {}
func (c *NoCommand) Undo() {}

2.4 步骤 4: 创建 Invoker

// Invoker: 遥控器
type RemoteControl struct {// 我们简化一下,只有一个按钮和撤销按钮slot CommandlastCommand Command // 用于撤销
}
func NewRemoteControl() *RemoteControl {return &RemoteControl{slot: &NoCommand{}, // 初始化为空命令,避免空指针检查lastCommand: &NoCommand{},}
}
func (r *RemoteControl) SetCommand(command Command) {r.slot = command
}
func (r *RemoteControl) ButtonWasPressed() {r.slot.Execute()r.lastCommand = r.slot // 记录最后执行的命令
}
func (r *RemoteControl) UndoButtonWasPressed() {r.lastCommand.Undo()
}

2.5 步骤 5: 客户端代码

func main() {// 1. 创建接收者light := &Light{}stereo := &Stereo{}// 2. 创建命令对象,并指定接收者lightOn := NewLightOnCommand(light)lightOff := NewLightOffCommand(light)stereoOn := NewStereoOnWithCDCommand(stereo)// 3. 创建调用者(遥控器)remote := NewRemoteControl()// --- 场景1: 打开电灯 ---fmt.Println("--- 场景1: 打开电灯 ---")remote.SetCommand(lightOn)remote.ButtonWasPressed()// --- 场景2: 撤销打开电灯 ---fmt.Println("\n--- 场景2: 撤销打开电灯 ---")remote.UndoButtonWasPressed()// --- 场景3: 打开音响 ---fmt.Println("\n--- 场景3: 打开音响 ---")remote.SetCommand(stereoOn)remote.ButtonWasPressed()// --- 场景4: 撤销打开音响 ---fmt.Println("\n--- 场景4: 撤销打开音响 ---")remote.UndoButtonWasPressed()
}

2.6 完整代码

下面是完整代码。

package main
import "fmt"
// =============================================================================
// 1. 定义 Command 接口
// =============================================================================
// Command 命令接口
type Command interface {Execute()Undo()
}
// =============================================================================
// 2. 创建 Receiver
// =============================================================================
// Receiver 1: 电灯
type Light struct {isOn bool
}
func (l *Light) On() {l.isOn = truefmt.Println("电灯已打开")
}
func (l *Light) Off() {l.isOn = falsefmt.Println("电灯已关闭")
}
// Receiver 2: 音响
type Stereo struct {isOn   boolvolume int
}
func (s *Stereo) On() {s.isOn = truefmt.Println("音响已打开")
}
func (s *Stereo) Off() {s.isOn = falsefmt.Println("音响已关闭")
}
func (s *Stereo) SetVolume(volume int) {s.volume = volumefmt.Printf("音响音量设置为 %d\n", volume)
}
// =============================================================================
// 3. 创建 ConcreteCommand
// =============================================================================
// ConcreteCommand 1: 打开电灯的命令
type LightOnCommand struct {light *Light
}
func NewLightOnCommand(light *Light) *LightOnCommand {return &LightOnCommand{light: light}
}
func (c *LightOnCommand) Execute() {c.light.On()
}
func (c *LightOnCommand) Undo() {c.light.Off()
}
// ConcreteCommand 2: 关闭电灯的命令
type LightOffCommand struct {light *Light
}
func NewLightOffCommand(light *Light) *LightOffCommand {return &LightOffCommand{light: light}
}
func (c *LightOffCommand) Execute() {c.light.Off()
}
func (c *LightOffCommand) Undo() {c.light.On()
}
// ConcreteCommand 3: 打开音响并设置音量的命令
type StereoOnWithCDCommand struct {stereo     *StereoprevVolume int // 用于撤销
}
func NewStereoOnWithCDCommand(stereo *Stereo) *StereoOnWithCDCommand {return &StereoOnWithCDCommand{stereo: stereo}
}
func (c *StereoOnWithCDCommand) Execute() {c.prevVolume = c.stereo.volume // 保存之前的状态c.stereo.On()c.stereo.SetVolume(11)
}
func (c *StereoOnWithCDCommand) Undo() {c.stereo.SetVolume(c.prevVolume) // 恢复之前的状态c.stereo.Off()
}
// ConcreteCommand 4: 空命令,用于初始化
type NoCommand struct{}
func (c *NoCommand) Execute() {}
func (c *NoCommand) Undo()   {}
// =============================================================================
// 4. 创建 Invoker
// =============================================================================
// Invoker: 遥控器
type RemoteControl struct {// 我们简化一下,只有一个按钮和撤销按钮slot        CommandlastCommand Command // 用于撤销
}
func NewRemoteControl() *RemoteControl {return &RemoteControl{slot:        &NoCommand{}, // 初始化为空命令,避免空指针检查lastCommand: &NoCommand{},}
}
func (r *RemoteControl) SetCommand(command Command) {r.slot = command
}
func (r *RemoteControl) ButtonWasPressed() {r.slot.Execute()r.lastCommand = r.slot // 记录最后执行的命令
}
func (r *RemoteControl) UndoButtonWasPressed() {r.lastCommand.Undo()
}
// =============================================================================
// 5. 客户端代码 (已修正)
// =============================================================================
func main() {// 1. 创建接收者light := &Light{}stereo := &Stereo{}// 2. 创建命令对象,并指定接收者lightOn := NewLightOnCommand(light)lightOff := NewLightOffCommand(light) // 这个变量现在会被使用stereoOn := NewStereoOnWithCDCommand(stereo)// 3. 创建调用者(遥控器)remote := NewRemoteControl()// --- 场景1: 打开电灯 ---fmt.Println("--- 场景1: 打开电灯 ---")remote.SetCommand(lightOn)remote.ButtonWasPressed()// --- 场景2: 撤销打开电灯 ---fmt.Println("\n--- 场景2: 撤销打开电灯 ---")remote.UndoButtonWasPressed()// --- 场景3: 直接关闭电灯 (使用 lightOff 命令) ---fmt.Println("\n--- 场景3: 直接关闭电灯 ---")remote.SetCommand(lightOff)remote.ButtonWasPressed()// --- 场景4: 撤销关闭电灯 ---fmt.Println("\n--- 场景4: 撤销关闭电灯 ---")remote.UndoButtonWasPressed()// --- 场景5: 打开音响 ---fmt.Println("\n--- 场景5: 打开音响 ---")remote.SetCommand(stereoOn)remote.ButtonWasPressed()// --- 场景6: 撤销打开音响 ---fmt.Println("\n--- 场景6: 撤销打开音响 ---")remote.UndoButtonWasPressed()
}

2.7 执行结果

现在,代码可以正常编译并运行,输出结果也更加完整:

--- 场景1: 打开电灯 ---
电灯已打开
--- 场景2: 撤销打开电灯 ---
电灯已关闭
--- 场景3: 直接关闭电灯 ---
电灯已关闭
--- 场景4: 撤销关闭电灯 ---
电灯已打开
--- 场景5: 打开音响 ---
音响已打开
音响音量设置为 11
--- 场景6: 撤销打开音响 ---
音响音量设置为 0
音响已关闭

总结:命令模式是一种非常强大且灵活的行为型模式。它通过将请求封装成对象,赋予了请求更多的生命力,使其可以被传递、存储、排队和撤销。在 Go 语言中,通过接口和结构体可以非常清晰地实现这一模式。虽然可能会导致类的数量增加,但它带来的解耦和可扩展性在很多复杂系统中是至关重要的。

http://www.dtcms.com/a/564516.html

相关文章:

  • Dropout提升模型泛化能力【动手学深度学习:PyTorch版 4.6 暂退法】
  • 网站开发用什么软件有哪些安徽安庆
  • 能够沟通业务的网站彩票网站开发 违法
  • 【机器学习13】异常检测优化、推荐系统、协同过滤
  • can‘t read /etc/apt/sources.list: No such file or directory
  • 深入理解 DNS 与 ICMP:网络世界的地址解析与连通性探测
  • MCU中的RC电路(Resistor-Capacitor Circuit)
  • Flink SQL 调优
  • CISP-PTE认证考试靶场
  • RDPWD!MCSAttachUserRequest函数分析之RDPWD!Domain结构中的ChannelList和UserAttachmentList
  • 细数Java中List的10个坑
  • 泉州手机网站开发怎么看一个网站是什么程序做的
  • PyTorch图像分割训练全流程解析
  • 无人机 - 关于无人机电池
  • 音视频播放的核心处理流程
  • 基于EasyExcel实现Excel导出功能
  • 【SpringBoot】31 核心功能 - 单元测试 - JUnit5 单元测试中的断言机制——验证你的代码是否按预期执行了
  • kafka问题解决
  • Parasoft C/C++test如何在CCS3环境下进行F2812项目的单元测试
  • CCID工具,Jenkins、GitLab CICD、Arbess一文全方位对比分析
  • 公司网页设计的设计过程南昌网站排名优化报价
  • 如何查询网站空间寻甸马铃薯建设网站
  • Node.js 中的中间件机制与 Express 应用
  • 【保姆级教程】在AutoDL容器中部署EGO-Planner,实现无人机动态避障规划
  • 仿生机器鹰无人机技术解析
  • 2025无人机在电力交通中的应用实践
  • Qt实时绘制飞行轨迹/移动轨迹实时显示/带旋转角度/平滑移动/效果一级棒/地面站软件开发/无人机管理平台
  • 八股已死、场景当立(场景篇-负载均衡篇)
  • Go语言设计模式:备忘录模式详解
  • 基于YOLOv10的无人机智能巡检系统:电力线路悬挂物检测实战