一、什么是命令(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實現都自己實現了所有功能,根本不需要額外的接收者對象。
三、總結
有時必須向某對象提交請求,但並不知道關於被請求的操作或請求的接受者的任何信息。這個時候可以用到命令模式,通過將請求本身變成一個對象來使工具箱對象可向未指定的應用對象提出請求。
