Open dushaoshuai opened 1 year ago
命令模式将操作的调用者和操作的执行者解耦。在命令模式中,Invoker(Sender)调用 Command,Command 自己不执行操作,而是将请求传递给真正的执行者 - Receiver 去执行。处于简化代码的考虑,Command 和 Receiver 也可以合并。Command 应该自己保存数据(包括请求和对 Receiver 的引用),这样他自己可以独立执行。使用 Command 模式,可以实现 undo/redo 操作(可能并不容易)、可以调度(如延后/重试)对请求的执行、可以减少代码重复(不同的 Invoker 可以调用相同的 Command,而不是都实现相同的代码逻辑)。
一个简单的实现
go-redis 使用了命令模式。
redis 的 PING 命令对应于 go-redis 中这样一个方法:
PING
// https://github.com/redis/go-redis/blob/a38f75b640398bd709ee46c778a23e80e09d48b5/commands.go#L536 func (c cmdable) Ping(ctx context.Context) *StatusCmd { cmd := NewStatusCmd(ctx, "ping") _ = c(ctx, cmd) return cmd }
在这段代码中,c cmdable 是 Invoker,cmd 是用 NewStatusCmd 函数构造的一个 Command,"ping" 是请求参数,由 cmd 携带。通过 c(ctx, cmd) 这样的方式,c 调用了 cmd。
c cmdable
cmd
NewStatusCmd
"ping"
c(ctx, cmd)
c
cmdable 是一个函数,接收 Cmder 参数:
cmdable
Cmder
// https://github.com/redis/go-redis/blob/a38f75b640398bd709ee46c778a23e80e09d48b5/commands.go#L448 type cmdable func(ctx context.Context, cmd Cmder) error
Cmder 是一个接口,定义了一组方法:
type Cmder interface { Name() string FullName() string Args() []interface{} String() string stringArg(int) string // ... }
任何实现了 Cmder 接口的类型都是 Command。 在 command.go 中,有许多类型都实现了 Cmder(这里是创建型模式中的工厂方法模式?),如 StatusCmd:
StatusCmd
// https://github.com/redis/go-redis/blob/a38f75b640398bd709ee46c778a23e80e09d48b5/command.go#L533 type StatusCmd struct { baseCmd val string } func NewStatusCmd(ctx context.Context, args ...interface{}) *StatusCmd { return &StatusCmd{ baseCmd: baseCmd{ ctx: ctx, args: args, }, } } func (cmd *StatusCmd) SetVal(val string) { cmd.val = val } // ...
函数 NewStatusCmd 接收请求参数 args 并将其保存在返回的 StatusCmd 中。
args
再来看 cmdable 的一个实现,提供了失败重试机制:
// https://github.com/redis/go-redis/blob/a38f75b640398bd709ee46c778a23e80e09d48b5/redis.go#L359 func (c *baseClient) process(ctx context.Context, cmd Cmder) error { var lastErr error for attempt := 0; attempt <= c.opt.MaxRetries; attempt++ { attempt := attempt retry, err := c._process(ctx, cmd, attempt) if err == nil || !retry { return err } lastErr = err } return lastErr }
命令模式将操作的调用者和操作的执行者解耦。在命令模式中,Invoker(Sender)调用 Command,Command 自己不执行操作,而是将请求传递给真正的执行者 - Receiver 去执行。处于简化代码的考虑,Command 和 Receiver 也可以合并。Command 应该自己保存数据(包括请求和对 Receiver 的引用),这样他自己可以独立执行。使用 Command 模式,可以实现 undo/redo 操作(可能并不容易)、可以调度(如延后/重试)对请求的执行、可以减少代码重复(不同的 Invoker 可以调用相同的 Command,而不是都实现相同的代码逻辑)。
一个简单的实现
一个简单的实现
github.com/redis/go-redis
go-redis 使用了命令模式。
redis 的
PING
命令对应于 go-redis 中这样一个方法:在这段代码中,
c cmdable
是 Invoker,cmd
是用NewStatusCmd
函数构造的一个 Command,"ping"
是请求参数,由cmd
携带。通过c(ctx, cmd)
这样的方式,c
调用了cmd
。cmdable
是一个函数,接收Cmder
参数:Cmder
是一个接口,定义了一组方法:任何实现了
Cmder
接口的类型都是 Command。 在 command.go 中,有许多类型都实现了Cmder
(这里是创建型模式中的工厂方法模式?),如StatusCmd
:函数
NewStatusCmd
接收请求参数args
并将其保存在返回的StatusCmd
中。再来看
cmdable
的一个实现,提供了失败重试机制:See also