Functional Options
今天我来讲一下在go语言编程中一种很常用的编程模式 - Functional Options 模式。Functional Options 模式是目前在Go语言中最流行的一种编程模式, 在 Kubernetes 等开源项目中就有 Functional Options 的影子。接下来我们就来聊一聊 Functional Options 和它解决的问题。
常见的使用场景
在我们编程中,我们会经常性的需要对一个对象进行相关的配置。比如下面这个结构实体:
type Server struct {
Addr string
Port int
Protocol string
TLS *tls.Config
}
在这个 Server 对象中,我们可以看到: 有侦听的IP地址 Addr 和端口号 Port ,这两个配置选项是必填的。 然后,还有协议 Protoco 字段,这几个字段是不能为空的,但是有默认值的,例如协议的默认值是tcp。 还有一个 TLS 这个是 HTTPS 安全链接,需要配置相关的证书和私钥。这个是可以为空的, 为空就是不使用 HTTPS。
所以根据上述结构体的描述,我们需要有多种不同的创建不同配置 Server 的函数签名,如下所示:
func NewDefaultServer(addr string, port int) (*Server, error) {
return &Server{addr, port, "tcp", nil}, nil
}
func NewTLSServerWithMaxConnAndTimeout(addr string, port int, tls *tls.Config) (*Server, error) {
return &Server{addr, port, "tcp", tls}, nil
}
因为go语言与Python等语言是不同的,没用重载函数,所你得用不同的函数名来应对不同的配置选项。
新增配置对象
要解决这个问题,最常见的方式是使用一个配置对象,如下所示:
type Config struct {
Protocol string
TLS *tls.Config
}
type Server struct {
Addr string
Port int
Conf *Config
}
我们将那些不是必填的字段存入新的结构体中,于是Server结构体就变成如上图所示的样子。 于是,我们只需要一个 NewServer() 的函数了,在使用前需要构造 Config 对象。
func NewServer(addr string, port int, conf *Config) (*Server, error) {
}
conf := ServerConfig{Protocol:"tcp"}
srv, _ := NewServer("locahost", 9000, &conf)
这个代码要对 config 对象判断是否为 nil 。
使用 Functional Options 模式
接下来针对上面的场景,我们使用 Functional Options 模式进行编程。
首先我们先定义个函数类型:
type Option func(*Server)
然后我们根据需求定义如下一组函数
func Protocol(p string) Option {
return func(s *Server) {
s.Protocol = p
}
}
func TLS(tls *tls.Config) Option {
return func(s *Server) {
s.TLS = tls
}
}
上面这组代码传入一个参数,然后返回一个函数,返回的这个函数会设置自己的 Server 结构体的参数。例如:
当我们调用其中的一个函数用 Protocol(“tcp”) 时 其返回值是一个 func(s* Server) { s.Protocol = “tcp” } 的函数。 这个叫高阶函数在一些函数式编程语言中是经常使用的,可能不太好理解。
好了,现在我们再定一个 NewServer()的函数,其中,有一个可变参数 options 其可以传出多个上面上的函数,然后使用一个for-loop来设置我们的 Server 对象。
unc NewServer(addr string, port int, options ...func(*Server)) (*Server, error) {
srv := Server{
Addr: addr,
Port: port,
Protocol: "tcp",
TLS: nil,
}
for _, option := range options {
option(&srv)
}
return &srv, nil
}
于是根据上面的定义,我们可以这样使用函数s1, _ := NewServer(“localhost”, 1024),这样就是使用 Functional Options 模式解决上面的问题。
Functional Options 模式带来了如下好处:
- 直觉式的编程
- 高度的可配置化
- 很容易维护和扩展
- 对于新来的人很容易上手
如下就是著名的 Kubernetes 项目中使用到的 Functional Options 模式 的一段代码。
// Option is a functional option type for Kubelet
type Option func(*Kubelet)
func WithRunAllFilters(runAllFilters bool) Option {
return func(o *frameworkOptions) {
o.runAllFilters = runAllFilters
}
}
// WithPodNominator sets podNominator for the scheduling frameworkImpl.
func WithPodNominator(nominator framework.PodNominator) Option {
return func(o *frameworkOptions) {
o.podNominator = nominator
}
}
// WithExtenders sets extenders for the scheduling frameworkImpl.
func WithExtenders(extenders []framework.Extender) Option {
return func(o *frameworkOptions) {
o.extenders = extenders
}
}
以后在遇到类似的问题时,推荐大家使用 Functional Options 模式。