Go 语言开源项目使用的函数选项模式

开发 项目管理
本文我们介绍怎么使用 Go 语言的 “函数选项模式”,通过阅读完本文所有内容,读者朋友们应该已经感受到该模式的优点。

​1.介绍

在阅读 Go 语言开源项目的源码时,我们可以发现有很多使用 “函数选项模式”  的代码,“函数选项模式” 是 Rob Pike 在 2014 年提出的一种模式,它使用 Go 语言的两大特性,变长参数和闭包,可以使我们代码更优雅。

关于变长参数和闭包的介绍,需要的读者朋友们可以查阅历史文章,本文我们介绍 “函数选项模式” 的相关内容。

2.使用方式

在介绍“函数选项模式”的使用方式之前,我们先阅读以下这段代码。

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 的内容。

3.进阶使用方式

所谓 “函数选项模式” 的进阶使用方式,即有返回值的 “函数选项模式”,其中,返回值包含 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 类型。

4.使用示例

我们在了解完 “函数选项模式” 之后,使用该模式实现一个简单示例。

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 类型零值作为默认值),而且还可以使调用者灵活传参(无需关心参数的顺序和个数)。

5.总结

本文我们介绍怎么使用 Go 语言的 “函数选项模式”,通过阅读完本文所有内容,读者朋友们应该已经感受到该模式的优点。

但是,该模式也有缺点,比如需要定义 WithXxx 函数,增加了代码量。

所以,我们可以根据实际使用场景决定是否选择使用 “函数选项模式”。

责任编辑:武晓燕 来源: Golang语言开发栈
相关推荐

2021-11-28 22:33:01

Go选项模式

2022-07-04 14:41:31

Go 语言变长参数变长参数函数

2022-07-03 23:07:48

Go语言参数

2022-04-13 08:20:32

DockerGo项目

2013-05-28 09:43:38

GoGo语言并发模式

2018-09-20 17:30:01

2021-04-13 07:58:42

Go语言函数

2019-01-22 15:32:05

Go语言工具开发

2019-04-26 09:37:30

Go 开源技术

2022-07-19 12:25:29

Go

2009-12-28 09:09:34

Google开源项目

2023-03-21 07:57:37

Go语言设计模式

2022-03-13 23:51:39

Web项目Go

2022-05-16 10:58:12

Go 项目Makefilemake

2023-03-27 00:20:48

2022-03-27 23:11:39

Go语言函数

2023-04-09 23:09:59

Go语言函数

2024-01-06 08:16:19

init​函数数据开发者

2021-10-16 17:53:35

Go函数编程

2022-04-30 18:42:38

Go编程语言
点赞
收藏

51CTO技术栈公众号