设计模式-命令模式(Go语言描述)

Posted 亓斌

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了设计模式-命令模式(Go语言描述)相关的知识,希望对你有一定的参考价值。

在上一篇博客设计模式-单例模式(Go语言描述)中我们介绍了在golang中如何去实现单例模式,在文章的最后我们也介绍到了golang独有的一种实现单例的方式-sync.Once.Do(),可以让golang轻松的实现可以应对并发请求的单利.今天我们继续探索设计模式,来介绍一下命令模式的实现.

说起命令,大家第一反应可能就是我们平时敲的各种命令,啪啪啪几行命令下去就可以完成一些功能,在看到命令模式这个词后,可能大家也会和我一样认为这里的命令就是执行一些简单任务的功能,然而并不是,这里的命令更多的像是我们发出的请求或者电视遥控器的按键.这样吧,咱们先来看看命令模式的定义,然后再从生活中找实际的例子对比一下.

命令模式是将一个请求封装为一个对象,从而使我们可用不同的请求对客户进行参数化;对请求排队或者记录请求日志,以及支持可撤销的操作.命令模式是一种对象行为型模式,其别名为动作模式或事务模式.

在命令模式中有如下几个角色:
Command: 命令
Invoker: 调用者
Receiver: 接受者
Client: 客户端

他们的关系可以这么来描述:

客户端通过调用者发送命令,命令调用接收者执行相应操作.

其实命令模式也很简单,不过不知道大家发现没有,在上述描述中调用者和接收者并不知道对方的存在,也就是说他们之间是解耦合的.

还是用遥控器的例子来解释一下吧,遥控器对应上面的角色就是调用者,电视就是接收者,命令呢?对应的就是遥控器上的按键,最后客户端就是我们人啦,当我们想打开电视的时候,就会通过遥控器(调用者)的电源按钮(命令)来打开电视(接收者),在这个过程中遥控器是不知道电视的,但是电源按钮是知道他要控制谁的什么操作.

… 哎呀, 语文不太好,突然发现越描述越不容易让人理解了呢? 还是用代码来实现一下上面遥控器的例子吧.我们对照着上面的角色一个个的去实现.
接收者,也就是一台大大的电视,

// receiver
type TV struct{}

func (p TV) Open() {
    fmt.Println("play...")
}

func (p TV) Close() {
    fmt.Println("stop...")
}

这台电视弱爆了,只有打开和关闭两个功能,对应的就是上面代码中的OpenClose两个方法,虽然很简单,不过我们确实造出了一台电视(估计还是彩色的).
下面,我们再来实现命令,也就是遥控器上的按键,因为电视只有打开和关闭功能,所以按键我们也只提供两个,多了用不上.

// command
type Command interface {
    Press()
}

type OpenCommand struct {
    tv TV
}

func (p OpenCommand) Press() {
    p.tv.Open()
}

type CloseCommand struct {
    tv TV
}

func (p CloseCommand) Press() {
    p.tv.Close()
}

首先我们定义了一个命令接口,只有一个方法就是Press,当我们按下按键时会去调用这个方法,然后我们果然只实现了两个按键,分别是OpenCommandCloseCommand,这两个实现中都保存着一台电视的句柄,并且在Press方法中根据功能去调用了这个tv的相应方法来完成正确的操作.

还有什么我们没有实现? 调用者,也就是遥控器了,来看看遥控器怎么实现吧.

// sender
type Invoker struct {
    cmd Command
}

func (p *Invoker) SetCommand(cmd Command) {
    p.cmd = cmd
}

func (p Invoker) Do() {
    p.cmd.Press()
}

在遥控器中我们有一个Command类型的变量,并且提供了SetCommand方法来设置命令,那我们按下遥控器上的键对应哪个方法呢?看看Do方法,我们直接调用了命令的Press方法,这个时候客户端(就是我们人)拿起遥控器,瞅准了打开按钮(SetCommand),并且按钮该键(Do),按键被按下(Press),电视打开了(Open).

那么最后,来看看客户端怎么去调用吧,

func main() {
    var tv TV
    openCommand := OpenCommand{tv}
    invoker := Invoker{openCommand}
    invoker.Do()

    invoker.SetCommand(CloseCommand{tv})
    invoker.Do()
}

命令模式实现起来也很简单,它降低了我们这个系统的耦合度,并且我们还可以任意拓展命令使用而不需要修改代码,开闭原则体现的淋漓尽致.

现在大家应该对命令模式有了一个直观的认识吧, 不过大家有没有发现一个问题,我们的遥控器在使用某个命令的时候只有set后才能用,也就是说每次只能使用一个命令,那有没有更好的办法来预装命令呢?肯定是有的,这就是我们接下来要介绍的复合命令,也叫做宏命令,复合命令其实说白了就是将多个命令保存起来,通过遍历这个集合来分别调用各个命令.

func NewOpenCloseCommand() *OpenCloseCommand {
    var openClose *OpenCloseCommand = &OpenCloseCommand{}
    openClose.cmds = make([]Command, 2)
    return openClose
}

type OpenCloseCommand struct {
    index int
    cmds  []Command
}

func (p *OpenCloseCommand) AddCommand(cmd Command) {
    p.cmds[p.index] = cmd
    p.index++
}

func (p OpenCloseCommand) Press() {
    for _, item := range p.cmds {
        item.Press()
    }
}

func main() {
  var openClose *OpenCloseCommand = NewOpenCloseCommand()
  openClose.AddCommand(OpenCommand{tv})
  openClose.AddCommand(CloseCommand{tv})
  invoker.SetCommand(openClose)
  invoker.Do()
}

我们定义了一个OpenCloseCommand,这里面用一个切片来保存各个命令,并通过AddCommand方法来王里面添加命令,OpenCloseCommand实现了Press方法,所以本质上他也是一个命令,我们调用他的Press方法来遍历保存的Command,并调用每一个的Press方法.

到这里,命令模式我们就介绍完了,命令模式具有很高的扩展性,遵循了开闭原则,所以减少了修改代码引入bug的可能性,快来想一想你的代码中哪些地方可以用命令模式来解决吧.

以上是关于设计模式-命令模式(Go语言描述)的主要内容,如果未能解决你的问题,请参考以下文章

设计模式-装饰者模式(Go语言描述)

设计模式-装饰者模式(Go语言描述)

GO语言实现设计模式全

GO语言实现设计模式全

设计模式-适配器模式(Go语言描写叙述)

设计模式-策略模式(Go语言描写叙述)