假如我们在日常工作中想为自己的团队或者在github等开源社区上提供一些库和模块,如果代码中有自定义结构体,那创建结构体的时候必然需要配置属性参数;这时动态灵活的配置不同的参数以及提供默认配置项就显得格外重要,使用函数式选项(functional options)模式就可以很好的解决这个问题。
type Server struct {
maxConn int
id string
tls bool
}
func NewServer(maxConn int, id string, tls bool) *Server {
return &Server{
maxConn: maxConn,
id: id,
tls: tls,
}
}
func main() {
server := NewServer(100, 'test', true)
fmt.Println(server)
}
运行结果:&{100 test true}
上面的例子中, 我们定义了一个结构体Server,并提供一个NewServer函数来创建Server。很显然,这样写的方式有两个比较突出的缺点存在:
NewServer函数的参数是固定的
如果结构体Server的字段属性非常多,那么对应的NewServer函数参数也是一样非常多,这将会变得非常繁琐
没有默认的字段属性
这里,可能有人会说可以创建一个Option或者Config结构体来接收这些属性,例如上面的代码可以优化写成如下的模式:
type Opt struct {
maxConn int
id string
tls bool
}
type Server struct {
opt *Opt
}
func NewServer(opt *Opt) *Server {
return &Server{
opt: opt,
}
}
func main() {
server := NewServer(&Opt{
100,
'test',
true,
})
fmt.Println(server.opt)
}
其实这样写已经是很不错了,很多的开源库作者都是这样写,引入一个Option结构体来解决多个参数的问题。
熟悉Java的同学都知道,平常开发中每写一个类都要写对应的get()/set()方法,其实Go的函数式选项也是类似这样的思想原理。
这里废话不多说,直接贴代码:
// OptFunc 定义一个OptFunc函数类型,用来更新Opt
type OptFunc func(*Opt)
type Opt struct {
maxConn int
id string
tls bool
}
type Server struct {
opt *Opt
}
// update maxConn
func withMaxConn(maxConn int) OptFunc {
return func(opt *Opt) {
opt.maxConn = maxConn
}
}
// update id
func withId(id string) OptFunc {
return func(opt *Opt) {
opt.id = id
}
}
// update tls
func withTls(tls bool) OptFunc {
return func(opt *Opt) {
opt.tls = tls
}
}
// default config
func defaultOpt() *Opt {
return &Opt{maxConn: 10,
id: 'test',
tls: true}
}
func NewServer(opts ...OptFunc) *Server {
o := defaultOpt()
for _, opt := range opts {
opt(o)
}
return &Server{
opt: o,
}
}
func main() {
server := NewServer(withId('update'), withTls(false), withMaxConn(100))
fmt.Println(server.opt)
}
运行结果:&{100 update false}
首先定义一个定义一个OptFunc函数类型,用来更新Opt结构体;
type OptFunc func(*Opt)
在配置化中说到的第三点缺点:没有默认配置,这里这样解决,直接创建一个函数 defaultOpt()来初始化Opt结构体
// default config
func defaultOpt() *Opt {
return &Opt{maxConn: 10,
id: 'test',
tls: true}
}
模仿Java的set()方法,这里我用三个withXX函数来更新对应的属性,返回一个OptFunc
// update maxConn
func withMaxConn(maxConn int) OptFunc {
return func(opt *Opt) {
opt.maxConn = maxConn
}
}
// update id
func withId(id string) OptFunc {
return func(opt *Opt) {
opt.id = id
}
}
// update tls
func withTls(tls bool) OptFunc {
return func(opt *Opt) {
opt.tls = tls
}
}
重写NewServer的入参,改用接收不定长的OptFunc,使函数参数接收更加灵活多变
func NewServer(opts ...OptFunc) *Server {
o := defaultOpt()
for _, opt := range opts {
opt(o)
}
return &Server{
opt: o,
}
}
如果有研究过GRPC源码的同学可能会发现,其使用的就是functional options模式。总结一下functional options的优点:
可以不定长入参
支持默认值
很容易维护和扩展
functional options 的优点很多,但是有些简单的配置其实并不需要这样小题大作,使简单问题复杂化。
联系客服