在看uber的golang最佳实践时,如果初始化一个结构时,在功能迭代中,初始化的参数不断地增加或改变,怎么才能让初始化结构变得可扩展。
uber提出的解决方案是功能选项。
几种备选方案#
横冲直幢型#
这是初学者最容易做到的,也是最不理想的,当我们每添加一项,就起一个新的初始化方法
1
2
3
4
5
6
|
type HttpServer struct{
Host string
Port string
}
func NewHttpServer(host, port string) *HttpServer{}
|
当功能需要我们http服务器能同时支持tls时, 我们可能就换成以下写法
1
2
3
4
5
6
7
8
9
|
type HttpServer struct{
Host string
Port string
Cert tls.Cert
Timeout int32
}
func NewHttpServerWithTls(host,port string) *HttpServer{}
func NewHttpServerWithTimeOut(host,port string, timeOut int32) *HttpServer{}
|
这种写法是没有任何扩展性可言的,当加入更多的配置选项,比如maxConn(最大并发连接数)等,导致针对不同的配置项,都可能产生一种组合,这种方式明显不太可取
没有一个config解决不了的事#
显然,上面的方法太不灵活了,只适用于基本不动的配置处理,对于可能新增的选项,修改起来就是灾难。
当然,我们可以用万能的config作参数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
type HttpServer struct{
Host string
Config *Config
}
type Config struct {
Port string
Cert tls.Config
Timeout int32
}
func NewHttpServer(host, &Config{
Port:8888,
})*HttpServer
|
每次有新的配置加入时,我们可以直接塞进config选项中,这样,我们只需要根据不同的传参来解决扩展性问题。
但当我们以为这是种比较完美的解决方案时,实际上,并没有想的那么美好。
它在默认值方面存在问题,例如,在此处显示的配置结构中,如果未提供 Port,NewServer 将返回 *Server 以侦听端口 8080。
但这有一个缺点,即您无法再显式将 Port 设置为 0 并让操作系统自动选择空闲端口,因为显式 0 与字段的零值无法区分。
更好的方式,功能选项#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
type Option func(*HttpServer)
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
}
}
func NewHttpServer(host string, options ...func(*Server))*HttpServer{
httpServer := &NewHttpServer{host}
for _,v := range options{
v(&httpServer)
}
}
s := NewHttpServer("localhost")
s1 := NewHttpServer("0.0.0.0", Timeout(300*time.Second), MaxConns(1000))
|
功能选项能完美的解决以上问题。有不少知名的库也采用该方法初始化
文末有该文章的参考文献,14年就有大佬提出的方案,到现在也毫不过时,不得不佩服。
参考文献#
https://dave.cheney.net/2014/10/17/functional-options-for-friendly-apis
https://github.com/xxjwxc/uber_go_guide_cn#%E5%8A%9F%E8%83%BD%E9%80%89%E9%A1%B9
https://coolshell.cn/articles/21146.html