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


    什么是函数选项模式

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

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

    而三方平台提供的一些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. 针对每个参数都有对应的设置函数,每个选项函数的实现代码量好像多了一些;
  • 相关阅读:
    如何设计神经网络结构图,神经网络设计与实现
    Unity UGUI的Outline(描边)组件的介绍及使用
    Java内存区域及类加载详解
    使用Conda
    总结Rabbitmq的六种模式
    AI艺术‘美丑’不可控?试试 AI 美学评分器~
    【MIT6.824】lab2C-persistence, lab2D-log compaction 实现笔记
    中国大学MOOC微观经济学答案
    【大数据实训】—Hadoop开发环境搭建(一)
    【组成原理 六 存储器类型】
  • 原文地址:https://www.cnblogs.com/taadis/p/go-functional-options-pattern.html