01 介绍
在阅读 Go 语言开源项目的源码时,我们可以发现有很多使用 “函数选项模式” 的代码,“函数选项模式” 是 Rob Pike 在 2014 年提出的一种模式,它使用 Go 语言的两大特性,变长参数和闭包,可以使我们代码更优雅。
关于变长参数和闭包的介绍,需要的读者朋友们可以查阅历史文章,本文我们介绍 “函数选项模式” 的相关内容。
02 使用方式
在介绍“函数选项模式”的使用方式之前,我们先阅读以下这段代码。
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)
}
输出结果:
&{Id:1 Name:}
&{Id:1 Name:frank}
阅读上面这段代码,我们可以发现,首先,我们定义一个名字是 option 的类型,它实际上是一个可以接收一个参数的函数。
然后,我们给 User 结构体定义一个 Option 方法,该方法接收我们定义的 option 类型的变长参数,方法体中使用 for-loop 执行函数。
定义 WithId 函数和 WithName 函数,设置 User 结构体的字段 Id 和字段 Name,该函数通过返回闭包的形式实现。
以上使用方式是 “函数选项模式” 的一般使用方式。该使用方式可以解决大部分问题,但是,“函数选项模式” 还有进阶使用方式,感兴趣的读者朋友们可以继续阅读 Part 03 的内容。
03 进阶使用方式
所谓 “函数选项模式” 的进阶使用方式,即有返回值的 “函数选项模式”,其中,返回值包含 golang 内置类型和自定义 option 类型。
内置类型的返回值
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)
}
输出结果:
1
&{Id:2 Name:}
阅读上面这段代码,我们在定义 option 类型时,使用一个有返回值函数(此处使用的是空接口类型的返回值)。
WithId 函数的函数体中的代码也稍作修改,闭包中使用 prevId 变量存储结构体 User 字段 Id 的原始数据,并作为函数返回值。
细心的读者朋友们可能已经发现,我们在 main 函数中显式处理返回值,即:
...
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)
}
输出结果:
&{Id:2 Name:}
&{Id:1 Name:}
阅读上面这段代码,我们在定义 option 类型时,通过把函数的返回值更改为 option 类型,我们就可以在 WithId 函数中,使用闭包处理 User 结构体 Id 字段的原始值。
需要注意的是, User 结构体 Option 方法的返回值是 option 类型。
04 使用示例
我们在了解完 “函数选项模式” 之后,使用该模式实现一个简单示例。
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)
}
输出结果:
&{Id:1000000001 Name:frank Email:undefined}
&{Id:-1 Name:guest Email:gopher@88.com}
&{Id:-1 Name:guest Email:undefined}
阅读上面这段代码,我们使用 “函数选项模式” 实现构造函数 NewUser,不仅可以自定义默认值(避免使用 Go 类型零值作为默认值),而且还可以使调用者灵活传参(无需关心参数的顺序和个数)。
05 kratos中的函数选项模式使用
定义结构体,其中ms是数组
// 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
}
定义没有返回值的函数选项,属性是数组的话也可以通过传递多个参数直接赋值
// 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出一个结构体,参数是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函数调用
httpSrv := http.NewServer(
http.Address(":8010"),
http.Middleware(
recovery.Recovery(),
),
)
06 总结
本文我们介绍怎么使用 Go 语言的 “函数选项模式”,通过阅读完本文所有内容,读者朋友们应该已经感受到该模式的优点。
但是,该模式也有缺点,比如需要定义 WithXxx 函数,增加了代码量。
所以,我们可以根据实际使用场景决定是否选择使用 “函数选项模式”。
网友评论