Go-Options模式

Options模式

Options模式可以让具有多个可选参数的函数或者方法更整洁和好扩展,当一个函数具有五六个甚至十个以上的可选参数时使用这种模式的优势会体现的很明显

问题

当我们发送一个Http请求,可能需要携带很多参数。比如:

func HttpRequest(method string, url string, body []byte, headers map[string]string, timeout time.Duration) 

有的时候又可能有些参数是不需要的,所以可能变成了:

HttpRequest('GET', 'https://www.baidu.com', nil, nil, 2 * time.Second)

解决办法一:把配置全转换为结构体对象

type HttpClientConfig struct {
timeout time.Duration
headers map[string]string
body []byte
}

func HttpRequest(method string, url string, config *HttpClientConfig)

不足

但是对于函数的实现方来说,仍然少不了那些选项参数非零值的判断,而且因为配置对象在函数外部可以改变,这就有一定几率配置对象在函数内部未被使用前被外部程序改变,真正发生了相关的BUG,排查起来会比较头

HttpRequest('GET', 'https://www.baidu.com', nil)

解决办法二-:可变参数方案的问题

func HttpRequest(method string, url string, options ...interface{})

虽然参数是可变的,但是实现方需要通过遍历设置HTTP客户端的不同选项,这就让可变参数固定了传递顺序,调用方如果想要设置某个可选项还得记住参数顺序,切无法直接通过函数签名就确定参数顺序,貌似还不如咱们最原始的解决方案。

解决办法三-使用Options模式的方案

  1. 设置原始的参数
// 针对可选的HTTP请求配置项,模仿gRPC使用的Options设计模式实现
type requestOption struct {
timeout time.Duration
data string
headers map[string]string
}

type Option struct {
apply func(option *requestOption)
}

func defaultRequestOptions() *requestOption {
return &requestOption{ // 默认请求选项
timeout: 5 * time.Second,
data: "",
headers: nil,
}
}
  1. 接下来我们要定义的配置函数,每个都会设置请求配置对象里的某一个配置

    func WithTimeout(timeout time.Duration) *Option {
    return &Option{
    apply: func(option *requestOption) {
    option.timeout = timeout
    },
    }
    }

    func WithData(data string) *Option {
    return &Option{
    apply: func(option *requestOption) {
    option.data = data
    },
    }
    }
  2. 定义func

    func HttpRequest(method string, url string, options ...*Option)
  3. 实现func的参数配置

    func httpRequest(method string, url string, options ...*Option) {
    reqOpts := defaultRequestOptions() // 默认的请求选项
    // 实现了options的配置
    for _, opt := range options { // 在reqOpts上应用通过options设置的选项
    opt.apply(reqOpts)
    }
    // 创建请求对象
    req, err := http.NewRequest(method, url, strings.NewReader(reqOpts.data))

    // 设置请求头
    for key, value := range reqOpts.headers {
    req.Header.Add(key, value)
    }
    // 发起请求
    ......

    return
    }