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

01 介绍

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

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

02 使用方式

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

typeUserstruct{
Idint
Namestring
}

typeoptionfunc(*User)

func(u*User)Option(opts...option){
for_,opt:=rangeopts{
opt(u)
}
}

funcWithId(idint)option{
returnfunc(u*User){
u.Id=id
}
}

funcWithName(namestring)option{
returnfunc(u*User){
u.Name=name
}
}

funcmain(){
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:1Name:}
&{Id:1Name:frank}

阅读上面这段代码,我们可以发现,首先,我们定义一个名字是option的类型,它实际上是一个可以接收一个参数的函数。

然后,我们给User结构体定义一个Option方法,该方法接收我们定义的option类型的变长参数,方法体中使用-loop执行函数。

定义WithId函数和WithName函数,设置User结构体的字段Id和字段Name,该函数通过返回闭包的形式实现。

以上使用方式是 “函数选项模式” 的一般使用方式。该使用方式可以解决大部分问题,但是,“函数选项模式” 还有进阶使用方式,感兴趣的读者朋友们可以继续阅读 Part 03 的内容。

03 进阶使用方式

所谓 “函数选项模式” 的进阶使用方式,即有返回值的 “函数选项模式”,其中,返回值包含 内置类型和自定义option类型。

内置类型的返回值

typeUserstruct{
Idint
Namestring
}

typeoptionfunc(*User)interface{}

func(u*User)Option(opts...option)(idinterface{}){
for_,opt:=range(opts){
id=opt(u)
}
returnid
}

funcWithId(idint)option{
returnfunc(u*User)interface{}{
prevId:=u.Id
u.Id=id
returnprevId
}
}

funcmain(){
u1:=&User{Id:1}
id:=u1.Option(WithId(2))
fmt.Println(id.(int))
fmt.Printf("%+v\n",u1)
}

输出结果:

1
&{Id:2Name:}

阅读上面这段代码,我们在定义option类型时,使用一个有返回值函数(此处使用的是空接口类型的返回值)。

WithId函数的函数体中的代码也稍作修改,闭包中使用prevId结构体User字段Id的原始,并作为函数返回值。

细心的读者朋友们可能已经发现,我们在main函数中显式处理返回值,即:

...
id:=u1.Option(WithId(2))
fmt.Println(id.(int))
...

如果我们想要避免显式处理返回值,可以使用返回自定义option类型的返回值的形式。

自定义 option 类型的返回值

typeUserstruct{
Idint
Namestring
}

typeoptionfunc(*User)option

func(u*User)Option(opts...option)(prevoption){
for_,opt:=rangeopts{
prev=opt(u)
}
returnprev
}

funcWithId(idint)option{
returnfunc(u*User)option{
prevId:=u.Id
u.Id=id
returnWithId(prevId)
}
}

funcmain(){
u1:=&User{Id:1}
prev:=u1.Option(WithId(2))
fmt.Printf("%+v\n",u1)
u1.Option(prev)
fmt.Printf("%+v\n",u1)
}

输出结果:

&{Id:2Name:}
&{Id:1Name:}

阅读上面这段代码,我们在定义option类型时,通过把函数的返回值更改为option类型,我们就可以在WithId函数中,使用闭包处理User结构体Id字段的原始值。

需要注意的是,User结构体Option方法的返回值是option类型。

04 使用示例

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

typeUserstruct{
Idint
Namestring
Emailstring
}

typeoptionfunc(*User)

funcWithId(idint)option{
returnfunc(u*User){
u.Id=id
}
}

funcWithName(namestring)option{
returnfunc(u*User){
u.Name=name
}
}

funcWithEmail(emailstring)option{
returnfunc(u*User){
u.=email
}
}

funcNewUser(opts...option)*User{
const(
defaultId=-1
defaultName="guest"
defaultEmail="undefined"
)
u:=&User{
Id:defaultId,
Name:defaultName,
Email:defaultEmail,
}

for_,opt:=rangeopts{
opt(u)
}
returnu
}

funcmain(){
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:1000000001Name:frankEmail:undefined}
&{Id:-1Name:guestEmail:gopher@88.com}
&{Id:-1Name:guestEmail:undefined}

阅读上面这段代码,我们使用 “函数选项模式” 实现构造函数NewUser,不仅可以自定义默认值(避免使用 Go 类型零值作为默认值),而且还可以使调用者灵活传参(无需关心参数的顺序和个数)。

05 总结

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

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

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

原文链接:https://mp.weixin.qq.com/s/2jzg2PIK_esjTxSFMkp02A

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

给TA打赏
共{{data.count}}人
人已打赏
运维笔记

如何在 Linux 下使用 TC 优雅的实现网络限流

2023-10-10 18:31:52

运维笔记

Linux启动流程

2023-10-10 18:31:54

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索