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)模式”。