学习GO:功能选项/默认配置值模式
由于Go的设计方式,除了零值之外,没有办法为我们的类型定义默认值;所以通常我们使用这些零值作为默认值,并试图以一种有意义的方式实现我们的类型,但在某些情况下,这可能导致误用或意外的结果。
在这篇文章中,我将与你分享一个众所周知的模式,即功能选项,以及在处理需要非零值作为默认配置的类型时的一个具体用例。
什么是功能选项模式?
这是Rob Pike在2014年讨论的一种模式,它包括实现一个接收函数类型的变量函数,其构建方式允许我们在不破坏API的情况下添加实现该类型的新函数。
A 变体函数是一个可以接收N个相同类型的参数的函数,类似于。
func Print(vals ...string)
for _, val := range vals {
fmt.Println(val)
}
}
在可以像这样使用。
Print("hello", "world")
Print("one", "two", "three")
而函数类型是指定义为一个类型的函数,例如,标准库中一个众所周知的类型是net/http.HandlerFunc。
type HandlerFunc func(ResponseWriter, *Request)
所有这些都可以通过下面的代码得到更好的解释。
1type Option func(*http.Server) error
2
3func WithAddr(addr string) func(*http.Server) {
4 return func(s *http.Server) {
5 s.Addr = addr
6 }
7}
8
9func WithReadTimeout(t time.Duration) func(*http.Server) {
10 return func(s *http.Server) {
11 s.ReadTimeout = t
12 }
13}
14
15func NewServer(opts ...Option) http.Server {
16 var s http.Server
17
18 for _, opt := range opts {
19 opt(&s)
20 }
21
22 return s
23}
这可以像这样使用。
s := NewServer()
s := NewServer(WithReadTimeout(1*time.Second))
s := NewServer(WithAddr(":80"))
s := NewServer(WithAddr(":8080"), WithReadTimeout(100*time.Millisecond))
以这样的方式实例化http.Server ,我们能够为配置目的添加Options,当然这段代码可能看起来过于工程化,因为从字面上看我们可以这样做。
func NewServer(addr string, t time.Duration) {
return http.Server{
Addr: addr,
ReadTimeOut: t,
}
}
或者更简单,我们可以去掉这个函数。
s := http.Server{
Addr: addr,
ReadTimeOut: t,
}
但在定义默认配置值时,这种模式的使用情况会更好。
实现默认配置值
如果我们修改初始化器,我们可以添加默认值,比如。
1const (
2 ServerDefaultReadTimeout time.Duration = 100 * time.Millisecond
3 ServerDefaultWriteTimeout time.Duration = 100 * time.Millisecond
4 ServerDefaultAddress string = ":8080"
5)
6
7func NewServer(opts ...Option) http.Server {
8 s := http.Server{
9 Addr: ServerDefaultAddress,
10 ReadTimeout: ServerDefaultReadTimeout,
11 WriteTimeout: ServerDefaultWriteTimeout,
12 }
13
14 for _, opt := range opts {
15 opt(&s)
16 }
17
18 return s
19}
如果我们再进一步,我们还可以添加验证。
1type Option func(*http.Server) error
2
3func WithAddr(addr string) func(*http.Server) error {
4 return func(s *http.Server) error {
5 s.Addr = addr
6 return nil
7 }
8}
9
10func WithReadTimeout(t time.Duration) func(*http.Server) error {
11 return func(s *http.Server) error {
12 if t > time.Second {
13 return errors.New("timeout value not allowed")
14 }
15
16 s.ReadTimeout = t
17 return nil
18 }
19}
20
21func WithWriteTimeout(t time.Duration) func(*http.Server) error {
22 return func(s *http.Server) error {
23 if t > time.Second {
24 return errors.New("timeout value not allowed")
25 }
26
27 s.WriteTimeout = t
28
29 return nil
30 }
31}
32
33func NewServer(opts ...Option) (http.Server, error) {
34 s := http.Server{
35 Addr: ServerDefaultAddress,
36 ReadTimeout: ServerDefaultReadTimeout,
37 WriteTimeout: ServerDefaultWriteTimeout,
38 }
39
40 for _, opt := range opts {
41 if err := opt(&s); err != nil {
42 return http.Server{}, fmt.Errorf("option failed %w", err)
43 }
44 }
45
46 return s, nil
47}
这使得我们可以定义一个新的API,支持默认选项、验证和一个向后兼容的API,并允许我们在需要时进行扩展。