问题描述#
当使用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
}
|
虽然代码量变多了,但是对于调用者,或整个系统代码而言,是无侵入的,不会破坏整体的兼容性,对配置也做到了可扩展性和可插拔性。