Go 語言開源項目使用的函數(shù)選項模式
01? 介紹
在閱讀 Go 語言開源項目的源碼時,我們可以發(fā)現(xiàn)有很多使用 “函數(shù)選項模式” ?的代碼,“函數(shù)選項模式” 是 Rob Pike 在 2014 年提出的一種模式,它使用 Go 語言的兩大特性,變長參數(shù)和閉包,可以使我們代碼更優(yōu)雅。
關(guān)于變長參數(shù)和閉包的介紹,需要的讀者朋友們可以查閱歷史文章,本文我們介紹 “函數(shù)選項模式” 的相關(guān)內(nèi)容。
02? 使用方式
在介紹“函數(shù)選項模式”的使用方式之前,我們先閱讀以下這段代碼。
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)
}
輸出結(jié)果:
&{Id:1?Name:}
&{Id:1?Name:frank}
閱讀上面這段代碼,我們可以發(fā)現(xiàn),首先,我們定義一個名字是?option
?的類型,它實際上是一個可以接收一個參數(shù)的函數(shù)。
然后,我們給?User
?結(jié)構(gòu)體定義一個?Option
?方法,該方法接收我們定義的?option
?類型的變長參數(shù),方法體中使用?for-loop
?執(zhí)行函數(shù)。
定義?WithId
?函數(shù)和?WithName
?函數(shù),設置?User
?結(jié)構(gòu)體的字段?Id
?和字段?Name
,該函數(shù)通過返回閉包的形式實現(xiàn)。
以上使用方式是 “函數(shù)選項模式” 的一般使用方式。該使用方式可以解決大部分問題,但是,“函數(shù)選項模式” 還有進階使用方式,感興趣的讀者朋友們可以繼續(xù)閱讀 Part 03 的內(nèi)容。
03? 進階使用方式
所謂 “函數(shù)選項模式” 的進階使用方式,即有返回值的 “函數(shù)選項模式”,其中,返回值包含 golang 內(nèi)置類型和自定義?option
?類型。
內(nèi)置類型的返回值
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)
}
輸出結(jié)果:
1
&{Id:2?Name:}
閱讀上面這段代碼,我們在定義?option
?類型時,使用一個有返回值函數(shù)(此處使用的是空接口類型的返回值)。
WithId
?函數(shù)的函數(shù)體中的代碼也稍作修改,閉包中使用?prevId
?變量存儲結(jié)構(gòu)體?User
?字段?Id
?的原始數(shù)據(jù),并作為函數(shù)返回值。
細心的讀者朋友們可能已經(jīng)發(fā)現(xiàn),我們在?main
?函數(shù)中顯式處理返回值,即:
...
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)
}
輸出結(jié)果:
&{Id:2?Name:}
&{Id:1?Name:}
閱讀上面這段代碼,我們在定義?option
?類型時,通過把函數(shù)的返回值更改為?option
?類型,我們就可以在?WithId
?函數(shù)中,使用閉包處理?User
?結(jié)構(gòu)體?Id
?字段的原始值。
需要注意的是,?User
?結(jié)構(gòu)體?Option
?方法的返回值是??option
?類型。
04? 使用示例
我們在了解完 “函數(shù)選項模式” 之后,使用該模式實現(xiàn)一個簡單示例。
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)
}
輸出結(jié)果:
&{Id:1000000001?Name:frank?Email:undefined}
&{Id:-1?Name:guest?Email:gopher@88.com}
&{Id:-1?Name:guest?Email:undefined}
閱讀上面這段代碼,我們使用 “函數(shù)選項模式” 實現(xiàn)構(gòu)造函數(shù)?NewUser
,不僅可以自定義默認值(避免使用 Go 類型零值作為默認值),而且還可以使調(diào)用者靈活傳參(無需關(guān)心參數(shù)的順序和個數(shù))。
05? 總結(jié)
本文我們介紹怎么使用 Go 語言的 “函數(shù)選項模式”,通過閱讀完本文所有內(nèi)容,讀者朋友們應該已經(jīng)感受到該模式的優(yōu)點。
但是,該模式也有缺點,比如需要定義?WithXxx
?函數(shù),增加了代碼量。
所以,我們可以根據(jù)實際使用場景決定是否選擇使用 “函數(shù)選項模式”。
原文鏈接:https://mp.weixin.qq.com/s/2jzg2PIK_esjTxSFMkp02A