Go 语言开源项目使用的函数选项模式

简介: Go 语言开源项目使用的函数选项模式

01

介绍

在阅读 Go 语言开源项目的源码时,我们可以发现有很多使用 “函数选项模式”  的代码,“函数选项模式” 是 Rob Pike 在 2014 年提出的一种模式,它使用 Go 语言的两大特性,变长参数和闭包,可以使我们代码更优雅。

关于变长参数和闭包的介绍,需要的读者朋友们可以查阅历史文章,本文我们介绍 “函数选项模式” 的相关内容。

使用方式

在介绍“函数选项模式”的使用方式之前,我们先阅读以下这段代码。

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

总结

本文我们介绍怎么使用 Go 语言的 “函数选项模式”,通过阅读完本文所有内容,读者朋友们应该已经感受到该模式的优点。

但是,该模式也有缺点,比如需要定义 WithXxx 函数,增加了代码量。

所以,我们可以根据实际使用场景决定是否选择使用 “函数选项模式”。

推荐阅读:

参考资料:

  1. https://commandcenter.blogspot.com/2014/01/self-referential-functions-and-design.html
  2. https://dave.cheney.net/2014/10/17/functional-options-for-friendly-apis


目录
相关文章
|
6天前
|
存储 JSON 监控
Viper,一个Go语言配置管理神器!
Viper 是一个功能强大的 Go 语言配置管理库,支持从多种来源读取配置,包括文件、环境变量、远程配置中心等。本文详细介绍了 Viper 的核心特性和使用方法,包括从本地 YAML 文件和 Consul 远程配置中心读取配置的示例。Viper 的多来源配置、动态配置和轻松集成特性使其成为管理复杂应用配置的理想选择。
23 2
|
4天前
|
Go 索引
go语言中的循环语句
【11月更文挑战第4天】
13 2
|
4天前
|
Go C++
go语言中的条件语句
【11月更文挑战第4天】
15 2
|
4天前
|
Go
go语言中的 跳转语句
【11月更文挑战第4天】
12 4
|
4天前
|
JSON 安全 Go
Go语言中使用JWT鉴权、Token刷新完整示例,拿去直接用!
本文介绍了如何在 Go 语言中使用 Gin 框架实现 JWT 用户认证和安全保护。JWT(JSON Web Token)是一种轻量、高效的认证与授权解决方案,特别适合微服务架构。文章详细讲解了 JWT 的基本概念、结构以及如何在 Gin 中生成、解析和刷新 JWT。通过示例代码,展示了如何在实际项目中应用 JWT,确保用户身份验证和数据安全。完整代码可在 GitHub 仓库中查看。
17 1
|
10天前
|
JavaScript Java Go
探索Go语言在微服务架构中的优势
在微服务架构的浪潮中,Go语言以其简洁、高效和并发处理能力脱颖而出。本文将深入探讨Go语言在构建微服务时的性能优势,包括其在内存管理、网络编程、并发模型以及工具链支持方面的特点。通过对比其他流行语言,我们将揭示Go语言如何成为微服务架构中的一股清流。
|
9天前
|
Ubuntu 编译器 Linux
go语言中SQLite3驱动安装
【11月更文挑战第2天】
31 7
|
9天前
|
关系型数据库 Go 网络安全
go语言中PostgreSQL驱动安装
【11月更文挑战第2天】
38 5
|
9天前
|
安全 Go
用 Zap 轻松搞定 Go 语言中的结构化日志
在现代应用程序开发中,日志记录至关重要。Go 语言中有许多日志库,而 Zap 因其高性能和灵活性脱颖而出。本文详细介绍如何在 Go 项目中使用 Zap 进行结构化日志记录,并展示如何定制日志输出,满足生产环境需求。通过基础示例、SugaredLogger 的便捷使用以及自定义日志配置,帮助你在实际开发中高效管理日志。
25 1
|
8天前
|
程序员 Go
go语言中的控制结构
【11月更文挑战第3天】
84 58