Written with StackEdit.

Functional options for friendly APIs

在一些框架代码中,比较常见的封装配置的方法是使用WithXXX()的API形态对外暴露接口,而非传统的Config配置数据结构。本文解释了配置API的集中方案。

原文地址:https://dave.cheney.net/2014/10/17/functional-options-for-friendly-apis

一个server package的列子

type Server struct {
listener net.Listener
}

func (s *Server) Addr() net.Addr
func (s *Server) Shutdown()

func NewServer(addr string) (*Server, error) {
l, err := net.Listen("tcp", addr)
if err != nil {
return nil, err
}
srv := Server(listener: l)
go srv.run()
return &srv, nil
}

Server暴露出来的API非常简单清晰,但是毫无扩展性。

功能扩展

最简单直接的方法,可能就是增加参数了。

func NewServer(addr string, 
clientTimeout time.Duration, // 超时时间
maxconns int, // 最大连接数
maxconcurrent int, // 最大并发
cert *tls.Cert) // 安全tls

带来的问题:

  1. 无法向前兼容的问题
  2. 默认值怎么填?如果我们不关心超时,clientTimeout要填什么?

可以根据使用场景,提供不同的constructor,但是这种方法过于笨重了。

// with timeout
NewServerWithTimeout(addr string, timeout time.Duration) (*Server, error)

// secure server
NewTlsServer(addr string, cert *tls.Cert) (*Server, error)

可配置

将配置单独抽离成Config。

type Config struct {
Cert *tls.Cert
Timeout time.Duration
}

func NewServer(addr string, config Config) *Server

优点:

  1. 向前兼容,增加参数对老版本兼容
  2. 文档化更方便,在Config中针对每项配置说明即可。

但是还是没有解决默认值的问题,很多时候大部分参数用户是不想去理解的。

“I just want a sever, I don’t want to have to think about it”

为了避免默认值带来影响,用户在调用的时候对不需要的参数不填默认值。

func NewServer(addr string, config ...Config) (*Server, error)

func main() {
svr, _ = NewServer("localhost") // default
srv2, _ := NewServer("localhost", Config{
Timeout: 300 * time.Second,
MaxConns: 10,
})
}

Functional Options

另一种方法就是将配置拆分成多份,以可变参数的方式传入settler:

func NewServer(addr string, options ...func(*Server)) (*Server, error) {
l, _ := net.Listen("tcp", addr)
srv := Server{listener: l}

for _, option := range(options) {
options(&srv)
}
return &srv, nil
}

// caller
func main(){
srv, _ := NewServer("localhost")

// timeout setter
timeout := func(srv *Server){
srv.timeout = 60 * time.Second
}
// tls settter
tls := func(srv *Server){
config := loadTlsConfig()
srv.listener = tls.NewListener(srv.listener, &config)
}
srv2, _ := NewServer("localhost", timeout, tls)
}

可以将各种setter function封装在package中,不用直接对外暴露config的内部数据结构,而是暴露API。

一个完整的demo如下:

// Server Package

package Server

import (
"fmt"
)

type Options struct {
Name string
Age int32
Addesss string
IdCard string
Cellphone string
}

type Option func(*Options)

func NewOptions(opts ...Option) (opt Options) {
for _, o := range opts {
o(&opt)
}
return
}

func Name(name string) Option {
return func(o *Options) {
o.Name = name
}
}

func Age(age int32) Option {
return func(o *Options) {
o.Age = age
}
}

func Address(addr string) Option {
return func(o *Options) {
o.Addesss = addr
}
}

// 用户调用demo
func main() {
var opt Options
opt = NewOptions(
Name("xigang"),
Age(24),
Address("Beijing"))
}