Go设计模式(26)-命令模式

命令模式很多同学可能不会用到,但这个模式还是蛮有意思的。命令模式能够将操作和数据打包成对象,便于系统对命令进行管理、维护。

UML类图位置:https://www.processon.com/view/link/60d29bf3e401fd49502afd25

本文代码链接为:https://github.com/shidawuhen/asap/blob/master/controller/design/26command.go

1.定义

1.1命令模式

命令模式:将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作。

UML:

图片

1.2分析

上面的定义和UML比较复杂,大家可能比较难理解。

首先我们需要明白什么是命令。命令包括指令和数据。指令是行为,数据影响到指令。如前进3米,前进是指令,3米是数据。

然后我们再看一下各个类的含义。

Command和ConcreteCommand是命令,有Excute函数,代表要做的行为。

ConcreteCommand调用Excute(),最终调用Receiver的Action。这意味ConcreteCommand只是一个容器,真正的操作逻辑在Receiver中。

Invoker包含了所有Command,控制Command何时执行Excute()。

现在我们将UML简化,把Invoker、Receiver去掉,看看是否容易理解了。

图片

通过这个简洁版UML,我们来看一下为什么要用命令模式。

命令包括指令和数据,指令其实对应着操作,操作在代码中对应着函数。

命令模式其实是把函数封装成对象,系统能对对象进行各种操作,如排队执行、记录日志、撤销等。

为什么要将函数包装成对象呢?C、C++、Go支持函数指针,但并不是所有语言都有这种特性,这时命令模式就起作用了。而且即使语言支持函数指针,命令的数据部分怎么存放仍是一个问题。

所以简单理解,命令模式就是把请求打包成一个一个Command对象,存储起来,系统根据实际需求进行处理。

2.应用场景

大家可能感觉命令模式与MQ、工厂模式一样,其实在细节上是有区别的:

  • MQ只包含数据,不包含行为,命令模式两者都包含

  • 工厂模式需要实时执行,但命令模式可以进行存储,延后执行

命令模式我从来没有用过。《设计模式之美》里讲了游戏研发的一种通用架构:客户端的请求被服务端存储起来,服务端有单独线程处理这些请求。那我们就按这个场景写一下代码实现。

3.代码实现

package main

import "fmt"

/**
 * @Author: Jason Pang
 * @Description: 命令接口
 */
type Command interface {
   Execute()
}

/**
 * @Author: Jason Pang
 * @Description: 移动命令
 */
type MoveCommand struct {
   x, y int64
}

/**
 * @Author: Jason Pang
 * @Description: 如何移动
 * @receiver m
 */
func (m *MoveCommand) Execute() {
   fmt.Printf("向右移动%d,向上移动%d \n", m.x, m.y)
}

/**
 * @Author: Jason Pang
 * @Description: 攻击命令
 */
type AttackCommand struct {
   skill string
}

/**
 * @Author: Jason Pang
 * @Description: 如何攻击
 * @receiver a
 */
func (a *AttackCommand) Execute() {
   fmt.Printf("使用技能%s\n", a.skill)
}

/**
 * @Author: Jason Pang
 * @Description: 记录命令
 * @param action
 * @return Command
 */
func AddCommand(action string) Command {
   if action == "attack" {
      return &AttackCommand{
         skill: "野蛮冲撞",
      }
   } else { //默认是移动
      return &MoveCommand{
         x: 10,
         y: 20,
      }
   }
}

func main() {
   //将命令记录
   lc := make([]Command, 0)
   lc = append(lc, AddCommand("attack"))
   lc = append(lc, AddCommand("move"))
   lc = append(lc, AddCommand("move"))
   lc = append(lc, AddCommand("attack"))

   //执行命令
   for _, c := range lc {
      c.Execute()
   }
}

输出:

➜ myproject go run main.go

使用技能野蛮冲撞

向右移动10,向上移动20

向右移动10,向上移动20

使用技能野蛮冲撞

通过上面的代码,大家应该能够理解命令模式了。可以看出,对不同请求,生成不同的Command,Command中包含对应的数据与操作。这也是模式定义中说到的”对请求排队或记录请求日志,以及支持可撤销的操作“。

总结

设计模式是为了解决现实中的问题,我们需要和具体场景相绑定。在解决问题的时候,采用的是不是标准的设计模式并不重要,模式只是手段,手段需要为达成目的服务。

最后

大家如果喜欢我的文章,可以关注我的公众号(程序员麻辣烫)

我的个人博客为:https://shidawuhen.github.io/

图片

往期文章回顾:

  1. 设计模式

  2. 招聘

  3. 思考

  4. 存储

  5. 算法系列

  6. 读书笔记

  7. 小工具

  8. 架构

  9. 网络

  10. Go语言

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员麻辣烫

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值