refer: Golang Functional Options Pattern
Go(Golang)的函数选项模式是一种在 Go 中设计结构体的方式,通过设计一组非常表达性和灵活的 API ,来帮助配置和初始化结构体。让我们看一个代码片段,看看我们可以使用哪些选项,以及函数选项模式何时如何有用。
例子:在 Go 中构建一个服务包
在这个例子中,我们看一下 Go 中的一个服务器包,但它也可以是任何被第三方客户端使用的东西,比如自定义 SDK 或日志记录库。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
package server
type Server {
host string
port int
}
func New(host string, port int) *Server {
return &Server{host, port}
}
func (s *Server) Start() error {
// todo
}
下方是一个客户端如何引入并且使用你的服务端包
1
2
3
4
5
6
7
8
9
10
11
12
13
package main
import (
"log"
"github.com/acme/pkg/server"
}
func main() {
svr := server.New("localhost", 1234)
if err := svr.Start(); err != nil {
log.Fatal(err)
}
}
现在,基于这个场景,我们如何扩展我们的配置选项?
这里有一些方法
- 为不同的配置选项申明一个新的构造方法
- 定义一个新的配置结构体用于存储配置信息
- 使用函数选项模式
让我们逐个探索这三个例子,并分析每个例子的优缺点。
选项 1:为不同的配置选项申明一个新的构造方法
如果您知道配置选项不太可能会更改,并且选项很少,那么这可能是一个不错的方法。因此,只需为每个不同的配置选项创建新方法就很容易了。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package server
type Server {
host string
port int
timeout time.Duration
maxConn int
}
func New(host string, port int) *Server {
return &Server{host, port, time.Minute, 100}
}
func NewWithTimeout(host string, port int, timeout time.Duration) *Server {
return &Server{host, port, timeout}
}
func NewWithTimeoutAndMaxConn(host string, port int, timeout time.Duration, macConn int) *Server {
return &Server{host, port, timeout, maxConn}
}
func (s *Server) Start() error {
// todo
}
相关的客户端实现如下
1
2
3
4
5
6
7
8
9
10
11
12
13
package main
import (
"log"
"github.com/acme/pkg/server"
)
func main() {
svr := server.NewWithTimeoutAndMaxConn("localhost", 1234, 30*time.Second, 10)
if err := svr.Start(); err != nil {
log.Fatal(err)
}
}
当配置选项的数量增加或经常变化时,这种方法就不太灵活了。您还需要为每个新的配置选项或一组配置选项创建新的构造函数。
选项 2:定义一个新的配置结构体用于存储配置信息
这是最常见的方法,在需要配置大量选项时可以很好地工作。您可以创建一个名为“ Config ”的新导出类型,其中包含服务器的所有配置选项。这样可以轻松扩展,而不会破坏服务器构造函数的 API 。当添加新选项或删除旧选项时,我们不必更改其定义。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package server
type Server {
cfg Config
}
type Config struct {
host string
port int
timeout time.Duration
maxConn int
}
func New(cfg Config) *Server {
return &Server{cfg}
}
func (s *Server) Start() error {
// todo
}
相关使用新配置结构体的客户端实现如下
1
2
3
4
5
6
7
8
9
10
11
12
13
package main
import (
"log"
"github.com/acme/pkg/server"
}
func main() {
svr := server.New(server.Config{"localhost", 1234, 30*time.Duration, 10})
if err := svr.Start(); err != nil {
log.Fatal(err)
}
}
这种方法灵活性较高,允许我们为服务器(或 SDK 客户端或您正在构建的任何内容)定义一个固定类型( server.Config
),以及一组稳定的API来配置我们的服务器,例如 server.New(cfg server.Config)
。唯一的问题是,当添加新选项或删除旧选项时,我们仍然需要对 Config 结构的结构进行重大更改。但迄今为止,这仍然是最好且更易用的选项。
选项 3:使用函数选项模式
解决这个选项配置问题的一个更好的选择是正好使用函数选项设计模式。您可能之前在 Go 项目中见过或听过函数选项模式,但在本例中,我们将详细分析其结构和特性。
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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
package server
type Server {
host string
port int
timeout time.Duration
maxConn int
}
func New(options ...func(*Server)) *Server {
svr := &Server{}
for _, o := range options {
o(svr)
}
return svr
}
func (s *Server) Start() error {
// todo
}
func WithHost(host string) func(*Server) {
return func(s *Server) {
s.host = host
}
}
func WithPort(port int) func(*Server) {
return func(s *Server) {
s.port = port
}
}
func WithTimeout(timeout time.Duration) func(*Server) {
return func(s *Server) {
s.timeout = timeout
}
}
func WithMaxConn(maxConn int) func(*Server) {
return func(s *Server) {
s.maxConn = maxConn
}
}
相关使用函数选项模式的客户端实现如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main
import (
"log"
"github.com/acme/pkg/server"
)
func main() {
svr := server.New(
server.WithHost("localhost"),
server.WithPort(8080),
server.WithTimeout(time.Minute),
server.WithMaxConn(120),
)
if err := svr.Start(); err != nil {
log.Fatal(err)
}
}
函数选项模式允许我们为服务器的每种可能的配置定义一个固定的类型签名,通过使用 func(*Server)
类型签名,我们可以创建要传递给服务器的任何选项。我们的选项默认也是可选的,因此可以轻松地在没有任何主要问题的情况下交换任何选项。这种方法还很好,因为类型定义具有表达性设计和自动文档化的特性,每个方法都定义了服务器的选项和选项类型。