命令模式與go-redis command設計

美味的糯米發表於2021-01-22

一、什麼是命令(Command)模式

命令模式是行為型設計模式的一種,其目的是將一個請求封裝為一個物件,從而使你可以用不同的請求對客戶進行引數化。與另一種將每種命令與呼叫命令的物件結合形成一個專有類的方式相比,命令模式的優點有將呼叫操作的物件與知道如何實現該操作的物件解耦,增加新的命令不需要修改現有的類。
命令模式的結構如下:
command
參與者有:
1.Invoker請求者
要求該命令執行這個請求,即命令的呼叫者
2.Command介面
3.ConcreteCommand具體介面
4.Receiver接收者
命令的相關操作的實際實施者
5.Client
協作過程:
1.Client建立一個ConcreteCommand物件並指定它的Receiver物件
2.某Invoker物件儲存該ConcreteCommand物件
3.該Invoker通過呼叫Command物件的Excute操作來提交一個請求。若該命令是可撤消的,ConcreteCommand就在執行Excute操作之前儲存當前狀態以用於取消該命令
4.ConcreteCommand物件對呼叫它的Receiver的一些操作以執行該請求

二、go-redis command相關程式碼

// commands.go

// Invoker請求者介面
type Cmdable interface {
      Pipeline() Pipeliner
      Pipelined(ctx context.Context, fn func(Pipeliner) error) ([]Cmder, error)
      TxPipelined(ctx context.Context, fn func(Pipeliner) error) ([]Cmder, error)
      TxPipeline() Pipeliner
      Command(ctx context.Context) *CommandsInfoCmd
      ClientGetName(ctx context.Context) *StringCmd
      // ...
      // 和所有Redis命令的相關方法
}

// cmdable實現了Cmdable介面
type cmdable func(ctx context.Context, cmd Cmder) error  
func (c cmdable) Echo(ctx context.Context, message interface{}) *StringCmd {
	cmd := NewStringCmd(ctx, "echo", message)
	_ = c(ctx, cmd)
	return cmd
}

這裡值得一提的是cmdable是一個函式型別,func(ctx context.Context, cmd Cmder) error
並且每個cmdable方法裡都會有_ = c(ctx, cmd),也就是如何去呼叫cmd在這裡還沒有明確寫出
再回頭看redis.go,會發現這樣一段程式碼

type Client struct {
      *baseClient
      cmdable
      hooks
      ctx context.Context
}

func NewClient(opt *Options) *Client {
      opt.init()

      c := Client{
            baseClient: newBaseClient(opt, newConnPool(opt)),
            ctx:        context.Background(),
      }
      c.cmdable = c.Process //劃線

      return &c
}

c.cmdable = c.Process這行指定了請求如何呼叫Command的
在ctrl+左鍵追蹤幾層後,會在redis.go裡找到呼叫的具體過程

// redis.go 
func (c *baseClient) process(ctx context.Context, cmd Cmder) error {
      ......
      err := cn.WithWriter(ctx, c.opt.WriteTimeout, func(wr *proto.Writer) error {
            eturn writeCmd(wr, cmd)
      })
				
      err = cn.WithReader(ctx, c.cmdTimeout(cmd), cmd.readReply)
      ......			

然後再去找Command,這邊就比較清晰了,都在command.go中

// command.go

// Command介面
type Cmder interface {
      Name() string
      FullName() string
      Args() []interface{}
      String() string
      stringArg(int) string
      firstKeyPos() int8
      setFirstKeyPos(int8)

      readTimeout() *time.Duration
      readReply(rd *proto.Reader) error
      
      SetErr(error)
      Err() error
}

// 還有許多Cmder的具體實現,其中一個實現的部分程式碼如下
type XPendingExtCmd struct {
      baseCmd
      val []XPendingExt
}
func (cmd *XPendingExtCmd) Val() []XPendingExt {
      return cmd.val
}

在這裡沒有看到Receiver,是因為每個Cmder實現都自己實現了所有功能,根本不需要額外的接收者物件。

三、總結

有時必須向某物件提交請求,但並不知道關於被請求的操作或請求的接受者的任何資訊。這個時候可以用到命令模式,通過將請求本身變成一個物件來使工具箱物件可向未指定的應用物件提出請求。

相關文章