最近在看gin的配置,然后发现配置方式很独特,通过了解原来是一种函数选项模式实现的,今天就顺便给自己做个笔记

Go函数选项模式

函数选项式(Functional Options) 是一种模式,在该模式中,你可以声明一个不透明的 Option 类型,该类型在某些内部结构体中记录信息。你接受这些可变数量的选项,并根据内部结构上的选项记录的完整信息进行操作。

将此模式用于构造函数和其他公共 API 中的可选参数,你预计这些参数需要扩展,尤其是在这些函数上已经有三个或更多参数的情况下。适合多个可选参数,初始化结构体来用,

既保持了兼容性,而且每增加1个新属性只需要1个With函数即可,大大减少了修改代码的风险

举个栗子-配置问题

type DbServer struct {
    User     string
    Password string
    Port     int           
    Host     string
    Charset  string
    MaxOpenConns int
}

//初始化一个Service
func NewDbServer(user string, password string, port int, host string) *DbServer {
      return &DbServer{user, password, name, port, host}
}

但是有个问题,因为golang支持函数重载,加入我们DbServer结构体中的其他属性也需要设置为非必填,该怎么处理

  1. 增加更多的NewService函数
  2. 在修改当前NewServer的入参,增加需要设置的属性

比如:除了user, password, port, host为必选字段外,charset是可选,如果增加其他可选,又需要再次增加函数

func NewDbServerWithCharset(user string, password string, port int, host string, charset string) *DbServer {
      return &DbServer{user, password, port, host, charset}
}

缺点:
1.创建太多的NewDbServer函数,增加开发量
2.代码冗余
3.无扩展性,如果再增加Server他属性呢

解决方式-使用函数选项模式

整个模式的实现分为三部分:

  1. 定义一个函数类型Options
  2. 定义一个New函数,可接收opts多个可选参数
  3. 用闭包方式为每个结构的可选属性设置With函数,主要的行为是给对应的DbServer结构体属性赋值

我们分别用栗子讲讲各步骤的具体实现

1.定义函数类型Option

type Option func(*DbServer)

2.闭包方式给每个结构体中的可选属性定义函数

func WithCharset(charset string) Option {
    return func(s *DbServer) {
        s.Charset = charset
    }
}

func WithMaxOpenConns(maxOpenConns int) Option {
    return func(s *DbServer) {
        s.MaxOpenConns = maxOpenConns
    }
}

3.定义NewOption

NewDbServer中的可选参数opts,类型是Option,也就是WithCharset, 或者WithMaxOpenConns。然后对参数进行遍历进行,执行option函数去对DbServer对应的属性进行修改

func NewDbServer(user string, password string, name string, port int, host string, opts ...Option) *Server {
    //创建DbServer对象,并填写可选项的默认值
    server := &DbServer{
       User:     user
       Password: password
       Port:     port           
       Host:     host
    }
    //都选项列表中每项都应用
    for _, option := range opts {
        option(server)
    }
    return s
}

使用的话就是

svr := NewDbServer(
    "username",
    "password",
    3306,
    "xxx",
    WithCharset("UTF-8"),
    WithMaxOpenConns(100),
  )

其实你会发现基本上框架的初始化都是使用了这种模式,比如go-micro

go-micro中的使用

总结

想必大家看完后也基本上了解了个大概了,但是这种方式最好的适用场景还是多参数配置上,而且是那种可选参数配置,如果我们只有两三个参数,且无后续扩展的话就没必要这么用了,反而会让我们程序显得更复杂不容易懂,好了今天的分享就到这里了

【记录分享点滴】