在我们编程中,我们会经常性的需要对一个对象(或是业务实体)进行相关的配置。比如下面这个业务实体(注意,这仅只是一个示例):
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
还是空)
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。