golang中的函數(shù)選項(xiàng)模式

01 介紹

在閱讀 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)容。

02 使用方式

在介紹“函數(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 的類(lèi)型,它實(shí)際上是一個(gè)可以接收一個(gè)參數(shù)的函數(shù)。

然后,我們給 User 結(jié)構(gòu)體定義一個(gè) Option 方法,該方法接收我們定義的 option 類(lèi)型的變長(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)容。

03 進(jìn)階使用方式

所謂 “函數(shù)選項(xiàng)模式” 的進(jìn)階使用方式,即有返回值的 “函數(shù)選項(xiàng)模式”,其中,返回值包含 golang 內(nèi)置類(lèi)型和自定義 option 類(lèi)型。

內(nèi)置類(lè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:}

閱讀上面這段代碼,我們?cè)诙x option 類(lèi)型時(shí),使用一個(gè)有返回值函數(shù)(此處使用的是空接口類(lèi)型的返回值)。

WithId 函數(shù)的函數(shù)體中的代碼也稍作修改,閉包中使用 prevId 變量存儲(chǔ)結(jié)構(gòu)體 User 字段 Id 的原始數(shù)據(jù),并作為函數(shù)返回值。

細(xì)心的讀者朋友們可能已經(jīng)發(fā)現(xiàn),我們?cè)?main 函數(shù)中顯式處理返回值,即:

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

如果我們想要避免顯式處理返回值,可以使用返回自定義 option 類(lèi)型的返回值的形式。

自定義 option 類(lèi)型的返回值

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 類(lèi)型時(shí),通過(guò)把函數(shù)的返回值更改為 option 類(lèi)型,我們就可以在 WithId 函數(shù)中,使用閉包處理 User 結(jié)構(gòu)體 Id 字段的原始值。

需要注意的是, User 結(jié)構(gòu)體 Option 方法的返回值是 option 類(lèi)型。

04 使用示例

我們?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 類(lèi)型零值作為默認(rèn)值),而且還可以使調(diào)用者靈活傳參(無(wú)需關(guān)心參數(shù)的順序和個(gè)數(shù))。

05 kratos中的函數(shù)選項(xiàng)模式使用

定義結(jié)構(gòu)體,其中ms是數(shù)組

// Server is an HTTP server wrapper.
type Server struct {
    *http.Server
    lis         net.Listener
    tlsConf     *tls.Config
    endpoint    *url.URL
    err         error
    network     string
    address     string
    timeout     time.Duration
    filters     []FilterFunc
    ms          []middleware.Middleware
    dec         DecodeRequestFunc
    enc         EncodeResponseFunc
    ene         EncodeErrorFunc
    strictSlash bool
    router      *mux.Router
    log         *log.Helper
}

定義沒(méi)有返回值的函數(shù)選項(xiàng),屬性是數(shù)組的話(huà)也可以通過(guò)傳遞多個(gè)參數(shù)直接賦值

// ServerOption is an HTTP server option.
type ServerOption func(*Server)

定義一系列的屬性賦值方法

// Network with server network.
func Network(network string) ServerOption {
    return func(s *Server) {
        s.network = network
    }
}

// Address with server address.
func Address(addr string) ServerOption {
    return func(s *Server) {
        s.address = addr
    }
}

// Timeout with server timeout.
func Timeout(timeout time.Duration) ServerOption {
    return func(s *Server) {
        s.timeout = timeout
    }
}

// Logger with server logger.
func Logger(logger log.Logger) ServerOption {
    return func(s *Server) {
        s.log = log.NewHelper(logger)
    }
}

// Middleware with service middleware option.
func Middleware(m ...middleware.Middleware) ServerOption {
    return func(o *Server) {
        o.ms = m
    }
}

// Filter with HTTP middleware option.
func Filter(filters ...FilterFunc) ServerOption {
    return func(o *Server) {
        o.filters = filters
    }
}

// RequestDecoder with request decoder.
func RequestDecoder(dec DecodeRequestFunc) ServerOption {
    return func(o *Server) {
        o.dec = dec
    }
}

// ResponseEncoder with response encoder.
func ResponseEncoder(en EncodeResponseFunc) ServerOption {
    return func(o *Server) {
        o.enc = en
    }
}

// ErrorEncoder with error encoder.
func ErrorEncoder(en EncodeErrorFunc) ServerOption {
    return func(o *Server) {
        o.ene = en
    }
}

// TLSConfig with TLS config.
func TLSConfig(c *tls.Config) ServerOption {
    return func(o *Server) {
        o.tlsConf = c
    }
}

// StrictSlash is with mux's StrictSlash
// If true, when the path pattern is "/path/", accessing "/path" will
// redirect to the former and vice versa.
func StrictSlash(strictSlash bool) ServerOption {
    return func(o *Server) {
        o.strictSlash = strictSlash
    }
}

// Listener with server lis
func Listener(lis net.Listener) ServerOption {
    return func(s *Server) {
        s.lis = lis
    }
}

new出一個(gè)結(jié)構(gòu)體,參數(shù)是option

// NewServer creates an HTTP server by options.
func NewServer(opts ...ServerOption) *Server {
    srv := &Server{
        network:     "tcp",
        address:     ":0",
        timeout:     1 * time.Second,
        dec:         DefaultRequestDecoder,
        enc:         DefaultResponseEncoder,
        ene:         DefaultErrorEncoder,
        strictSlash: true,
        log:         log.NewHelper(log.GetLogger()),
    }
    for _, o := range opts {
        o(srv)
    }
    srv.router = mux.NewRouter().StrictSlash(srv.strictSlash)
    srv.router.Use(srv.filter())
    srv.Server = &http.Server{
        Handler:   FilterChain(srv.filters...)(srv.router),
        TLSConfig: srv.tlsConf,
    }
    srv.err = srv.listenAndEndpoint()
    return srv
}

main函數(shù)調(diào)用

httpSrv := http.NewServer(
        http.Address(":8010"),
        http.Middleware(
            recovery.Recovery(),
        ),
    )

06 總結(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)模式”。


引用(本文章只供本人學(xué)習(xí)以及學(xué)習(xí)的記錄,如有侵權(quán),請(qǐng)聯(lián)系我刪除)

Go 語(yǔ)言開(kāi)源項(xiàng)目使用的函數(shù)選項(xiàng)模式

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容