学习GO:功能选项/默认配置值模式

129 阅读3分钟

学习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,并允许我们在需要时进行扩展。