脚本之家,脚本语言编程技术及教程分享平台!
分类导航

Python|VBS|Ruby|Lua|perl|VBA|Golang|PowerShell|Erlang|autoit|Dos|bat|

服务器之家 - 脚本之家 - Golang - Go 语言开源项目使用的函数选项模式

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

2022-11-08 20:30Golang语言开发栈frank Golang

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

1.介绍

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

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

2.使用方式

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

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 的内容。

3.进阶使用方式

所谓 “函数选项模式” 的进阶使用方式,即有返回值的 “函数选项模式”,其中,返回值包含 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 类型。

4.使用示例

我们在了解完 “函数选项模式” 之后,使用该模式实现一个简单示例。

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 类型零值作为默认值),而且还可以使调用者灵活传参(无需关心参数的顺序和个数)。

5.总结

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

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

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

原文地址:https://mp.weixin.qq.com/s/2jzg2PIK_esjTxSFMkp02A

延伸 · 阅读

精彩推荐
  • GolangGo语言异常处理(Panic和recovering)用法详解

    Go语言异常处理(Panic和recovering)用法详解

    异常处理是程序健壮性的关键,往往开发人员的开发经验的多少从异常部分处理上就能得到体现。Go语言中没有Try Catch Exception机制,但是提供了panic-and-re...

    孙琦Ray9502022-07-13
  • GolangGo:我有注解,Java:不,你没有!

    Go:我有注解,Java:不,你没有!

    作为一位 Go 程序员,你会发现身边的同事大多都拥有其他语言的编写经验。那势必就会遇到一点,要把新学到的知识和以前的知识建立连接。...

    脑子进煎鱼了4732021-10-15
  • GolangGolang gRPC HTTP协议转换示例

    Golang gRPC HTTP协议转换示例

    这篇文章主要为大家介绍了Golang gRPC HTTP协议转换示例,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪...

    Coldstar12002022-10-21
  • Golang深入解析golang中的标准库flag

    深入解析golang中的标准库flag

    Go语言内置的flag包实现了命令行参数的解析,flag包使得开发命令行工具更为简单,下面通过本文给大家详细介绍下golang中的标准库flag相关知识,感兴趣的...

    专职10942022-01-19
  • GolangGo 语言中的空接口(推荐)

    Go 语言中的空接口(推荐)

    这篇文章主要介绍了Go 语言中的空接口的实现方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下 ...

    Python编程时光3462020-07-16
  • Golanggo使用Gin框架利用阿里云实现短信验证码功能

    go使用Gin框架利用阿里云实现短信验证码功能

    这篇文章主要介绍了go使用Gin框架利用阿里云实现短信验证码,使用json配置文件及配置文件解析,编写路由controller层,本文通过代码给大家介绍的非常详细...

    月下繁星杨8672021-09-14
  • GolangGo语言中的Slice学习总结

    Go语言中的Slice学习总结

    这篇文章主要介绍了Go语言中的Slice学习总结,本文讲解了Slice的定义、Slice的长度和容量、Slice是引用类型、Slice引用传递发生“意外”等内容,需要的朋友可...

    junjie5012020-04-10
  • Golanggo语言Timer计时器的用法示例详解

    go语言Timer计时器的用法示例详解

    Go语言的标准库里提供两种类型的计时器Timer和Ticker。这篇文章通过实例代码给大家介绍go语言Timer计时器的用法,代码简单易懂,感兴趣的朋友跟随小编一...

    网管叨bi叨5952020-07-08