Go 函数式选项

在实例化一个含有多个字段的结构体时,往往会封装一个工厂函数,类似于

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 用于示例,实际情况可能有更多的字端
type T struct {
A string
B int
C int
D bool
}

func NewT(a string, b int, c int, d bool) *T {
return &T{
A: a,
B: c,
D: d,
}
}

但有些时候,我们只想对特定的字端进行赋值,而其他的字端则保持默认值。例如

1
2
3
4
5
6
7
8
9
10
func NewTWithA(a string) *T {
return &T{A: a}
}

func NewTWithAB(a string, b int) *T {
return &T{
A:a,
B:b,
}
}

由于 Go 不支持函数重载,对于多个不同字端组合赋值初始时,往往需要多个工厂函数。这样太过于麻烦,因此需要一些辅助的手段来辅助处理。

对于这种情况,Rob Pike 提出了一种优雅的解决方式,函数式选项。区别于使用对象式选项传入一个 Option 对象的方式,函数式选项传入一个配置函数序列,其中的每个函数都会对所需要的对象进行一定的配置,最终构建出所需要的对象。

使用函数式选项

首先定义选项 Option , 然后在使用闭包封装配置方法

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
type Option func(t *T) 

// Ta 将 T 的 A 设置为 a
func Ta(a string) Option {
return func(t *T) {
t.A = a
}
}

func Tb(b int) Option {
return func(t *T) {
t.B = b
}
}

func Tc(c int) Option {
return func(t *T) {
t.C = c
}
}

func Td(d bool) Option {
return func(t *T) {
t.D = d
}
}

// 同时设置 A,D 在某些情况下 A,D 存在相关性
func Tad(a string, d bool) Option {
return func(t *T) {
t.A = a
t.D = d
}
}

然后修改一下NewT,使用 Option 来配置 T

1
2
3
4
5
6
7
func NewT(ops ...Option) *T {
t := &T{}
for _, opt := range ops {
opt(t)
}
return t
}

最后来看一下使用的效果

1
2
3
4
5
6
7
8
9
10
11
12
13
func main() {
// 只设置 A
t := NewT(Ta("Ta"))
fmt.Printf("t is %v \n", t) // t is &{3 0 0 false}

// 设置 B,C,D
t2 := NewT(Tb(2), Tc(3), Td(true))
fmt.Printf("t is %v \n", t2) // t is &{ 2 3 true}

t3 := NewT(Tad("a and d", true))
fmt.Printf("t is %v \n", t3) // t is &{a and d 0 0 true}
}

通过这种方式,我们就可以自由的配置需要的对象参数,并且省去了定义一堆长名字工厂函数的麻烦。


Go 函数式选项
https://blog.zhangliangliang.cc/post/Golang-functional-option.html
作者
Bobby Zhang
发布于
2021年1月24日
许可协议