01
介绍
在阅读 Go 语言开源项目的源码时,我们可以发现有很多使用 “函数选项模式” 的代码,“函数选项模式” 是 Rob Pike 在 2014 年提出的一种模式,它使用 Go 语言的两大特性,变长参数和闭包,可以使我们代码更优雅。
关于变长参数和闭包的介绍,需要的读者朋友们可以查阅历史文章,本文我们介绍 “函数选项模式” 的相关内容。
使用方式
在介绍“函数选项模式”的使用方式之前,我们先阅读以下这段代码。
type User struct { Id int Name string } type option func(*User) func (u *User) Option(opts ...option) { for _, opt := range opts { opt(u) } } func WithId(id int) option { return func(u *User) { u.Id = id } } func WithName(name string) option { return func(u *User) { u.Name = name } } func main() { u1 := &User{} u1.Option(WithId(1)) fmt.Printf("%+v\n", u1) u2 := &User{} u2.Option(WithId(1), WithName("frank")) fmt.Printf("%+v\n", u2) }
输出结果:
&{Id:1 Name:} &{Id:1 Name:frank}
阅读上面这段代码,我们可以发现,首先,我们定义一个名字是 option
的类型,它实际上是一个可以接收一个参数的函数。
然后,我们给 User
结构体定义一个 Option
方法,该方法接收我们定义的 option
类型的变长参数,方法体中使用 for-loop
执行函数。
定义 WithId
函数和 WithName
函数,设置 User
结构体的字段 Id
和字段 Name
,该函数通过返回闭包的形式实现。
以上使用方式是 “函数选项模式” 的一般使用方式。该使用方式可以解决大部分问题,但是,“函数选项模式” 还有进阶使用方式,感兴趣的读者朋友们可以继续阅读 Part 03 的内容。
03
进阶使用方式
所谓 “函数选项模式” 的进阶使用方式,即有返回值的 “函数选项模式”,其中,返回值包含 golang 内置类型和自定义 option
类型。
内置类型的返回值
type User struct { Id int Name string } type option func(*User) interface{} func (u *User) Option(opts ...option) (id interface{}) { for _, opt := range(opts) { id = opt(u) } return id } func WithId(id int) option { return func(u *User) interface{} { prevId := u.Id u.Id = id return prevId } } func main () { u1 := &User{Id: 1} id := u1.Option(WithId(2)) fmt.Println(id.(int)) fmt.Printf("%+v\n", u1) }
输出结果:
1 &{Id:2 Name:}
阅读上面这段代码,我们在定义 option
类型时,使用一个有返回值函数(此处使用的是空接口类型的返回值)。
WithId
函数的函数体中的代码也稍作修改,闭包中使用 prevId
变量存储结构体 User
字段 Id
的原始数据,并作为函数返回值。
细心的读者朋友们可能已经发现,我们在 main
函数中显式处理返回值,即:
... id := u1.Option(WithId(2)) fmt.Println(id.(int)) ...
如果我们想要避免显式处理返回值,可以使用返回自定义 option
类型的返回值的形式。
自定义 option 类型的返回值
type User struct { Id int Name string } type option func(*User) option func (u *User) Option(opts ...option) (prev option) { for _, opt := range opts { prev = opt(u) } return prev } func WithId(id int) option { return func(u *User) option { prevId := u.Id u.Id = id return WithId(prevId) } } func main () { u1 := &User{Id: 1} prev := u1.Option(WithId(2)) fmt.Printf("%+v\n", u1) u1.Option(prev) fmt.Printf("%+v\n", u1) }
输出结果:
&{Id:2 Name:} &{Id:1 Name:}
阅读上面这段代码,我们在定义 option
类型时,通过把函数的返回值更改为 option
类型,我们就可以在 WithId
函数中,使用闭包处理 User
结构体 Id
字段的原始值。
需要注意的是, User
结构体 Option
方法的返回值是 option
类型。
04
使用示例
我们在了解完 “函数选项模式” 之后,使用该模式实现一个简单示例。
type User struct { Id int Name string Email string } type option func(*User) func WithId(id int) option { return func(u *User) { u.Id = id } } func WithName(name string) option { return func(u *User) { u.Name = name } } func WithEmail(email string) option { return func(u *User) { u.Email = email } } func NewUser(opts ...option) *User { const ( defaultId = -1 defaultName = "guest" defaultEmail = "undefined" ) u := &User{ Id: defaultId, Name: defaultName, Email: defaultEmail, } for _, opt := range opts { opt(u) } return u } func main() { u1 := NewUser(WithName("frank"), WithId(1000000001)) fmt.Printf("%+v\n", u1) u2 := NewUser(WithEmail("gopher@88.com")) fmt.Printf("%+v\n", u2) u3 := NewUser() fmt.Printf("%+v\n", u3) }
输出结果:
&{Id:1000000001 Name:frank Email:undefined} &{Id:-1 Name:guest Email:gopher@88.com} &{Id:-1 Name:guest Email:undefined}
阅读上面这段代码,我们使用 “函数选项模式” 实现构造函数 NewUser
,不仅可以自定义默认值(避免使用 Go 类型零值作为默认值),而且还可以使调用者灵活传参(无需关心参数的顺序和个数)。
05
总结
本文我们介绍怎么使用 Go 语言的 “函数选项模式”,通过阅读完本文所有内容,读者朋友们应该已经感受到该模式的优点。
但是,该模式也有缺点,比如需要定义 WithXxx
函数,增加了代码量。
所以,我们可以根据实际使用场景决定是否选择使用 “函数选项模式”。
推荐阅读:
参考资料:
- https://commandcenter.blogspot.com/2014/01/self-referential-functions-and-design.html
- https://dave.cheney.net/2014/10/17/functional-options-for-friendly-apis