问题描述

当使用Go语言去构建对象时,通常会调用相应的New方法,去进行初始化的操作。如下代码所示。 但若以后增加新的字段配置,则会破坏函数的兼容性,例如增加超时时间等。

1
2
3
4
5
6
func NewServer(addr string, port int) (server *http.Server, err error) {
	server = &http.Server{
		Addr: fmt.Sprintf("%s:%d", addr, port),
	}
	return
}

Functional Options Pattern

Functional Options Pattern简称FOP,是一种软件设计模式。它将函数的核心逻辑的实现,与它的可选参数(功能选项)的实现分开。

这样就可以轻松地修改和定制函数的行为,而不需要担心破坏函数的兼容性问题。

 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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
type options struct {
	port         *int // 字段参数,尽量使用指针的方式,去判定是否有设定该字段。
	handler      *http.Handler
	readTimeout  *time.Duration
	writeTimeout *time.Duration
}

type Option func(options *options) error

func WithPort(port int) Option {
	return func(options *options) error {
		if port < 0 {
			return errors.New("http port should be positive")
		}
		options.port = &port
		return nil
	}
}

func WithHandler(handler http.Handler) Option {
	return func(options *options) error {
		options.handler = &handler
		return nil
	}
}

func WithReadTimeout(r time.Duration) Option {
	return func(options *options) error {
		if r < 0 {
			return errors.New("http read timeout should be positive")
		}
		options.readTimeout = &r
		return nil
	}
}

func WithWriteTimeout(w time.Duration) Option {
	return func(options *options) error {
		if w < 0 {
			return errors.New("http read timeout should be positive")
		}
		options.writeTimeout = &w
		return nil
	}
}

func NewServer(addr string, opts ...Option) (server *http.Server, err error) {
	var options options
	for _, opt := range opts {
		if err = opt(&options); err != nil {
			return nil, err
		}
	}

	var port int
	if options.port == nil {
		port = DefaultHttpPort
	} else {
		if *options.port == 0 {
			port = int(rand.Uint64() % 3000)
		} else {
			port = *options.port
		}
	}

	var handler http.Handler
	if options.handler == nil {
		err = errors.New("http handler shouldn't be empty")
		return
	} else {
		handler = *options.handler
	}

	var writeTimeout time.Duration
	if options.writeTimeout == nil {
		writeTimeout = DefaultHttpTimeout
	} else {
		writeTimeout = *options.writeTimeout
	}

	var readTimeout time.Duration
	if options.readTimeout == nil {
		readTimeout = DefaultHttpTimeout
	} else {
		readTimeout = *options.readTimeout
	}

	server = &http.Server{
		Addr:         fmt.Sprintf("%s:%d", addr, port),
		Handler:      handler,
		WriteTimeout: writeTimeout,
		ReadTimeout:  readTimeout,
	}

	return server, nil
}

虽然代码量变多了,但是对于调用者,或整个系统代码而言,是无侵入的,不会破坏整体的兼容性,对配置也做到了可扩展性和可插拔性。