dushaoshuai / dushaoshuai.github.io

https://www.shuai.host
0 stars 0 forks source link

Command #75

Open dushaoshuai opened 1 year ago

dushaoshuai commented 1 year ago

命令模式将操作的调用者和操作的执行者解耦。在命令模式中,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 中这样一个方法:

// 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

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

// 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 中。

再来看 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
}

See also