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