一、简介在阅读Go语言开源项目的源代码时,我们可以发现有很多代码使用了“函数选项模式”。“函数选项模式”是RobPike在2014年提出的一种模式,它利用了Go语言的两大特性,变长参数和闭包,可以让我们的代码更加优雅。关于变长参数和闭包的介绍,有需要的读者可以参考历史文章。在本文中,我们介绍了“功能选项模式”的相关内容。2.如何使用在介绍如何使用“功能选项模式”之前,我们先来阅读下面的代码。typeUserstruct{IdintNamestring}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}看了上面的代码,我们可以发现,首先,我们定义了一个name为选项的类型,实际上是一个可以接收一个参数的函数。然后,我们为User结构体定义一个Option方法,它接收一个我们定义的option类型的变长参数,并使用for循环执行方法体中的函数。定义WithId函数和WithName函数,并设置User结构体的字段Id和字段Name。这个函数是通过返回一个闭包来实现的。以上用法是“功能选项模式”的一般用法。这种使用方法可以解决大部分问题。不过,“功能选项模式”还有更高级的使用方法。有兴趣的读者可以继续阅读Part03的内容。3.高级用法所谓“函数选项模式”的高级用法,就是带有返回值的“函数选项模式”,返回值包括golang内置的类型和自定义选项类型。内置类型的返回值typeUserstruct{idintNamestring}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.Idu.Id=idreturnprevId}}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))...如果我们想要避免显式处理返回值,我们可以使用返回自定义选项类型的返回值的形式。自定义选项类型的返回值typeUserstruct{idintNamestring}typeoptionfunc(*User)optionfunc(u*User)Option(opts...option)(prevoption){for_,opt:=rangeopts{prev=opt(u)}returnprev}funcWithId(idint)option{returnfunc(u*User)option{prevId:=u.Idu.Id=idreturnWithId(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:}看上面的代码,我们在定义optiontype的时候,把函数的返回值改成option类型,我们可以用一个闭包来处理WithId函数中User结构体Id字段的原始值。需要注意的是,User结构体的Option方法的返回值是option类型。4.使用示例在我们了解了“功能选项模式”之后,我们将使用该模式来实现一个简单的示例。typeUserstruct{IdintNamestringEmailstring}typeoptionfunc(*User)funcWithId(idint)option{returnfunc(u*User){u.Id=id}}funcWithName(namestring)option{返回func(u*User){u.Name=name}}funcWithEmail(emailstring)option{returnfunc(u*User){u.Email=email}}funcNewUser(opts...option)*User{const(defaultId=-1defaultName="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类型的零值作为默认值),还可以让调用者灵活传递参数(无需关心参数的顺序和个数)5.总结本文介绍如何使用Go语言的“函数选项模式”。看完本文的所有内容,读者朋友们应该已经感受到这种模式的优势了。但是这种模式也有缺点,比如需要定义WithXxx函数,增加了代码量。因此,我们可以根据实际使用场景来决定是否使用“功能选项模式”。
