?1.介紹
在閱讀 Go 語言開源項目的源碼時,我們可以發(fā)現(xiàn)有很多使用 “函數(shù)選項模式” 的代碼,“函數(shù)選項模式” 是 Rob Pike 在 2014 年提出的一種模式,它使用 Go 語言的兩大特性,變長參數(shù)和閉包,可以使我們代碼更優(yōu)雅。
關(guān)于變長參數(shù)和閉包的介紹,需要的讀者朋友們可以查閱歷史文章,本文我們介紹 “函數(shù)選項模式” 的相關(guān)內(nèi)容。
2.使用方式
在介紹“函數(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ù),設(shè)置 User? 結(jié)構(gòu)體的字段 Id? 和字段 Name,該函數(shù)通過返回閉包的形式實現(xiàn)。
以上使用方式是 “函數(shù)選項模式” 的一般使用方式。該使用方式可以解決大部分問題,但是,“函數(shù)選項模式” 還有進階使用方式,感興趣的讀者朋友們可以繼續(xù)閱讀 Part 03 的內(nèi)容。
3.進階使用方式
所謂 “函數(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é)果:
閱讀上面這段代碼,我們在定義 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 類型。
4.使用示例
我們在了解完 “函數(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ù))。
5.總結(jié)
本文我們介紹怎么使用 Go 語言的 “函數(shù)選項模式”,通過閱讀完本文所有內(nèi)容,讀者朋友們應該已經(jīng)感受到該模式的優(yōu)點。
但是,該模式也有缺點,比如需要定義 WithXxx 函數(shù),增加了代碼量。
所以,我們可以根據(jù)實際使用場景決定是否選擇使用 “函數(shù)選項模式”。