Functional Options Pattern(函数式选项模式)可用于传递不同选项配置到方法中,而且每次新增选项时,可以不改变接口保持兼容。还可以用来实现类似 Java/C++ 中方法重写的功能~
Go 中结构体没有构造方法,所以有的时候我们会自己提供一个 New 方法。考虑你有某个客户端结构体:
1
2
3
4
5
6
7
|
type Client struct {
// ...
}
func New() *Client {
return &Client{}
}
|
有一天需要新增一个 Timeout 参数,由于 Go 不支持方法重载,因此我们不得不修改 New 方法传入一个 Timeout 参数, 但是一旦修改了 New 的签名,将会造成不兼容。于是我们只好新增一个工厂方法 NewClientWithTimeout 并且修改旧有的 New 方法,设置它的 Timeout 为一个默认值。
但是下次再需要新增字段,还是很麻烦……
我们可以使用可变参数来解决这一问题。
先定义一个配置结构体:
1
2
3
4
5
|
type Config struct {
Timeout time.Duration
Cluster string
}
type Option func(*Config)
|
然后对每个配置项提供一个配置函数
1
2
3
4
5
6
7
8
9
10
|
func WithTimeout(timeout time.Duration) Option {
return func(config *Config) {
config.Timeout = timeout
}
}
func WithCluster(cluster string) Option {
return func(config *Config) {
config.Cluster = cluster
}
}
|
最后在 New 方法上使用可变参数来接收配置函数
1
2
3
4
5
6
7
8
9
10
11
12
|
func New(opts ...Option) *Client {
// default config
config := &Config{Timeout: 20 * time.Millisecond, Cluster: "default"}
// for each custom options
for _, opt := range opts {
opt(config)
}
return &Client{
Timeout: config.Timeout,
Cluster: config.Cluster,
}
}
|
下次再需要新增配置项,只需要新增 WithXxx 配置函数,修改默认 Config 的值即可。调用方可以直接升级版本,如果不传入新的配置函数,则会使用默认配置。
1
2
3
|
New()
New(WithTimeout(10 * time.Millisecond))
New(WithTimeout(10*time.Millisecond), WithCluster("my-cluster"))
|
这个模式还可以用来模仿 Java8 的接口默认实现、或者用来模仿 Java 中的子类重写(override)父类方法。
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 foo
// 暴露的接口
type MyInterface interface {
SayHi() string
}
// 实现类
type base struct {
// 得益于 Go 中函数是一等公民,可以将函数存在字段中
sayHi func() string
}
// 使用函数字段实现接口
func (b *base) SayHi() string {
return b.sayHi()
}
// 提供自定义能力
type Option func(*base)
func SayHi(f func() string) Option {
return func(b *base) {
b.sayHi = f
}
}
// 默认行为
func newDefault() *base {
return &base{
sayHi: func() string {
return "Hello, World"
},
}
}
// 构造方法
func New(opts ...Option) MyInterface {
b := newDefault()
for _, opt := range opts {
opt(b)
}
return b
}
|
使用方:
1
2
3
4
5
6
7
8
9
|
func ExampleNew() {
fmt.Println(foo.New().SayHi())
fmt.Println(foo.New(foo.SayHi(func() string {
return "你好,世界"
})).SayHi())
// Output:
// Hello, World
// 你好,世界
}
|
这样就可以通过传入函数的形式改变默认的行为,看起来像重写?
《Go Streams 仿照 Java8 的流造的轮子》中 stage 接口使用了这一模式。
Reader Echoes
No comments yet