在实例化一个含有多个字段的结构体时,往往会封装一个工厂函数,类似于
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)
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 } }
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() { t := NewT(Ta("Ta")) fmt.Printf("t is %v \n", t) t2 := NewT(Tb(2), Tc(3), Td(true)) fmt.Printf("t is %v \n", t2) t3 := NewT(Tad("a and d", true)) fmt.Printf("t is %v \n", t3) }
|
通过这种方式,我们就可以自由的配置需要的对象参数,并且省去了定义一堆长名字工厂函数的麻烦。