1

Go语言实践模式 - 函数选项模式(Functional Options Pattern) - taadis

 2 years ago
source link: https://www.cnblogs.com/taadis/p/go-functional-options-pattern.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

什么是函数选项模式

大家好,我是小白,有点黑的那个白。

最近遇到一个问题,因为业务需求,需要对接三方平台.

而三方平台提供的一些HTTP(S)接口都有统一的密钥生成规则要求.

为此我们封装了一个独立的包 xxx-go-sdk 以便维护和对接使用.

其中核心的部分是自定义HTTP Client,如下:

type Client struct {}

func (c *Client) do() {
      // 实现统一的加密和签名逻辑
      // 统一调用net/http
}

// 订单列表接口
func (c *Client) OrderList(){
      c.do()
}


// 订单发货接口
func (c *Client) OrderDelivery(){
      c.do()
}

// ... 其他接口

一些平台会要求appKey/appSecret等信息,所以Client结构体就变成了这样,这时参数还比较少, 而且是必填的参数,我们可以提供构造函数来明确指定。

type Client struct {
      AppKey     string
      AppSecret string
}

func NewClient(appKey string, appSecret string) *Client {
     c := new(Client)
     c.AppKey = appKey
     c.AppSecret = appSecret
     return c
}

看起来很满足,但是当我们需要增加一个 Timeout 参数来控制超时呢?

或许你会说这还不简单,像下面一样再加一个参数呗

type Client struct {
      AppKey     string
      AppSecret string
      Timeout    time.Duration
}

func NewClient(appKey string, appSecret string, timeout time.Duration) *Client {
     c := new(Client)
     c.AppKey = appKey
     c.AppSecret = appSecret
     c.Timeout = timeout
     return c
}

那再加些其他的参数呢?那构造函数的参数是不是又长又串,而且每个参数不一定是必须的,有些参数我们有会考虑默认值的问题。

为此,勤劳但尚未致富的 gophers 们使用了总结一种实践模式

首先提取所有需要的参数到一个独立的结构体 Options,当然你也可以用 Configs 啥的.

type Options struct {
      AppKey       string
      AppSecret string
}

然后为每个参数提供设置函数

func WithAppKey(appKey string) func(*Options) {
      return func(o *Options) {
            o.AppKey = appKey
      }
}

func WithAppSecret(appSecret string) func(*Options) {
      return func(o *Options) {
            o.AppSecret = appSecret
      }
}

这样我们就为每个参数设置了独立的设置函数。返回值 func(*Options) 看着有点不友好,我们提取下定义为单个 Option 调整一下代码

type Option func(*Options)

func WithAppKey(appKey string) Option {
      return func(o *Options) {
            o.AppKey = appKey
      }
}

func WithAppSecret(appSecret string) Option {
      return func(o *Options) {
            o.AppSecret = appSecret
      }
}

当我们需要添加更多的参数时,只需要在 Options 添加新的参数并添加新参数的设置函数即可。

比如现在要添加新的参数 Timeout

type Options struct {
      AppKey       string
      AppSecret   string
      Timeout.     time.Duration // 新增参数
}

// Timeout 的设置函数
func WithTimeout(timeout time.Duration) Option {
      return func(o *Options) {
            o.Timeout = timeout
      }
}

这样后续不管新增多少参数,只需要新增配置项并添加独立的设置函数即可轻松扩展,并且不会影响原有函数的参数顺序和个数位置等。

至此,每个选项是区分开来了,那么怎么作用到我们的 Client 结构体上呢?

首先,配置选项都被提取到了 Options 结构体中,所以我们需要调整一下 Client 结构体的参数

type Client struct {
      options *Options
}

其次,每一个选项函数返回 Option,那么任意多个就是 ...Option,我们调整一下构造函数 NewClient 的参数形式,改为可变参数,不在局限于固定顺序的几个参数。

func NewClient(options ...Option) *Client {
    c := new(Client)
    c.Options = ?
    return c
}

然后循环遍历每个选项函数,来生成Client结构体的完整配置选项。

func NewClient(options ...Option) *Client {
     opts := new(Options)
     for _, o := range options {
          o(opts)
    }
    c := new(Client)
    c.Options = opts
    return c
}

那么怎么调用呢?对于调用方而已,直接在调用构造函数NewClient()的参数内添加自己需要的设置函数(WithXXX)即可

client := NewClient(
     WithAppKey("your-app-key"),
     WithAppSecret("your-app-secret"),
)

当需要设置超时参数,直接添加 WithTimeout即可,比如设置3秒的超时

client := NewClient(
     WithAppKey("your-app-key"),
     WithAppSecret("your-app-secret"),
     WithTimeout(3*time.Second),
)

配置选项的位置可以任意设置,不需要受常规的固定参数顺序约束。

可以看到,这种实践模式主要作用于配置选项,利用函数支持的特性来实现的,为此得名 Functional Options Pattern,优美的中国话叫做「函数选项模式」。

最后, 我们总结回顾一下在Go语言中函数选项模式的优缺点

  1. 支持多参数;
  2. 支持参数任意位置顺序;
  3. 支持默认值设置;
  4. 向后兼容,扩展性极佳;
  5. 用户使用行为一致, 体感良好.

这是特性,不是缺点 - -!

  1. 增加了Options结构和Option定义;
  2. 针对每个参数都有对应的设置函数,每个选项函数的实现代码量好像多了一些;

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK