在咱们编程中,咱们会经常性的须要对一个对象(或是业务实体)进行相干的配置。比方上面这个业务实体(留神,这仅只是一个示例):
type Server struct {
Addr string
Port int
Protocol string
Timeout time.Duration
MaxConns int
TLS *tls.Config
}
在这个 Server 对象中,咱们能够看到:
要有侦听的 IP 地址 Addr
和端口号 Port
,这两个配置选项是必填的(当然,IP 地址和端口号都能够有默认值,当这里咱们用于举例认为是没有默认值,而且不能为空,须要必填的)。
而后,还有协定 Protocol
、Timeout
和 MaxConns
字段,这几个字段是不能为空的,然而有默认值的,比方:协定是 tcp, 超时 30 秒 和 最大链接数 1024 个。
还有一个 TLS
这个是平安链接,须要配置相干的证书和私钥。这个是能够为空的。
要解决这个问题,最常见的形式是应用一个配置对象,如下所示:
type Config struct {
Protocol string
Timeout time.Duration
Maxconns int
TLS *tls.Config
}
咱们把那些非必输的选项都移到一个构造体里,于是 Server 对象变成了:
type Server struct {
Addr string
Port int
Conf *Config
}
Functional Options
首先,咱们先定义一个函数类型:
type Option func(*Server)
而后,咱们能够应用函数式的形式定义一组如下的函数:
func Protocol(p string) Option {return func(s *Server) {s.Protocol = p}
}
func Timeout(timeout time.Duration) Option {return func(s *Server) {s.Timeout = timeout}
}
func MaxConns(maxconns int) Option {return func(s *Server) {s.MaxConns = maxconns}
}
func TLS(tls *tls.Config) Option {return func(s *Server) {s.TLS = tls}
}
下面这组代码传入一个参数,而后返回一个函数,返回的这个函数会设置本人的 Server 参数。例如:
当咱们调用其中的一个函数用 MaxConns(30)
时
其返回值是一个 func(s* Server) {s.MaxConns = 30}
的函数。
这个叫高阶函数。在数学上,就如同这样的数学定义,计算长方形面积的公式为:rect(width, height) = width * height
; 这个函数须要两个参数,咱们包装一下,就能够变成计算正方形面积的公式:square(width) = rect(width, width)
也就是说,squre(width)
返回了另外一个函数,这个函数就是 rect(w,h)
只不过他的两个参数是一样的。即:f(x) = g(x, x)
好了,当初咱们再定一个 NewServer()
的函数,其中,有一个可变参数 options
其能够传出多个下面上的函数,而后应用一个 for-loop
来设置咱们的 Server
对象。
func NewServer(addr string, port int, options ...func(*Server)) (*Server, error) {
srv := Server{
Addr: addr,
Port: port,
Protocol: "tcp",
Timeout: 30 * time.Second,
MaxConns: 1000,
TLS: nil,
}
for _, option := range options {option(&srv)
}
//...
return &srv, nil
}
于是,咱们在创立 Server 对象的时候,咱们就能够这样来了。
s1, _ := NewServer("localhost", 1024)
s2, _ := NewServer("localhost", 2048, Protocol("udp"))
s3, _ := NewServer("0.0.0.0", 8080, Timeout(300*time.Second), MaxConns(1000))
高度的整洁和优雅, 岂但解决了应用 Config
对象形式 的须要有一个 config
参数,但在不须要的时候,是放 nil
还是放 Config{}
的抉择艰难,也不须要援用一个 Builder
的管制对象,间接应用函数式编程的试,在代码浏览上也很优雅。
所以,当前,大家在要玩相似的代码时,强烈推荐应用 Functional Options
这种形式,这种形式至多带来了如下的益处:
- 直觉式的编程
- 高度的可配置化
- 很容易保护和扩大
- 自文档
- 对于新来的人很容易上手
- 没有什么令人困惑的事(是
nil
还是空)
参考资料
GO 编程模式:FUNCTIONAL OPTIONS