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)
|
带来的问题:
- 无法向前兼容的问题
- 默认值怎么填?如果我们不关心超时,clientTimeout要填什么?
可以根据使用场景,提供不同的constructor,但是这种方法过于笨重了。
NewServerWithTimeout(addr string, timeout time.Duration) (*Server, error)
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
|
优点:
- 向前兼容,增加参数对老版本兼容
- 文档化更方便,在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") 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 }
func main(){ srv, _ := NewServer("localhost")
timeout := func(srv *Server){ srv.timeout = 60 * time.Second } tls := func(srv *Server){ config := loadTlsConfig() srv.listener = tls.NewListener(srv.listener, &config) } srv2, _ := NewServer("localhost", timeout, tls) }
|
可以将各种setter function封装在package中,不用直接对外暴露config的内部数据结构,而是暴露API。
一个完整的demo如下:
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 } }
func main() { var opt Options opt = NewOptions( Name("xigang"), Age(24), Address("Beijing")) }
|