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


目录
相关文章
|
4天前
|
缓存 NoSQL Go
通过 SingleFlight 模式学习 Go 并发编程
通过 SingleFlight 模式学习 Go 并发编程
|
11天前
|
存储 编译器 Go
|
3天前
|
Go C语言
Go语言:新时代的编程英雄,让你的代码驾驭未来!
【8月更文挑战第29天】Go,或称Golang,是由谷歌开发的一种静态强类型的编译语言,旨在融合C语言的高效性和高级语言的易用性。它简洁、优雅,广泛应用于高性能服务器和网络应用开发。本文将通过环境搭建、Hello World示例、变量、常量、控制结构、函数、结构体、接口及错误处理等示例,带你快速入门Go语言,领略其简洁高效的魅力,激发你的编程热情。
|
3天前
|
存储 机器学习/深度学习 设计模式
Go从入门到放弃之函数
Go从入门到放弃之函数
|
6天前
|
安全 Java Go
探索Go语言在高并发场景中的优势
Go语言,作为一种现代编程语言,凭借其并发模型和高效的性能,正在成为处理高并发任务的首选。本文深入探讨Go语言的并发特性,特别是goroutines和channels如何在实际应用中提供高效的解决方案。我们将通过对比其他语言的处理方式,展示Go语言在性能和可维护性上的优势。
|
6天前
|
Shell Go 开发工具
【Azure Developer】Go语言调用Azure SDK如何登录到中国区Azure环境
【Azure Developer】Go语言调用Azure SDK如何登录到中国区Azure环境
|
10天前
|
缓存 监控 中间件
构建高效的Go语言Web服务器:基于Fiber框架的性能优化实践
在追求极致性能的Web开发领域,Go语言(Golang)凭借其高效的并发处理能力、垃圾回收机制及简洁的语法赢得了广泛的青睐。本文不同于传统的性能优化教程,将深入剖析如何在Go语言环境下,利用Fiber这一高性能Web框架,通过精细化配置、并发策略调整及代码层面的微优化,构建出既快速又稳定的Web服务器。通过实际案例与性能测试数据对比,揭示一系列非直觉但极为有效的优化技巧,助力开发者在快节奏的互联网环境中抢占先机。
|
11天前
|
算法 安全 Go
|
4月前
|
开发框架 安全 中间件
Go语言开发小技巧&易错点100例(十二)
Go语言开发小技巧&易错点100例(十二)
53 1
|
20天前
|
JSON 中间件 Go
go语言后端开发学习(四) —— 在go项目中使用Zap日志库
本文详细介绍了如何在Go项目中集成并配置Zap日志库。首先通过`go get -u go.uber.org/zap`命令安装Zap,接着展示了`Logger`与`Sugared Logger`两种日志记录器的基本用法。随后深入探讨了Zap的高级配置,包括如何将日志输出至文件、调整时间格式、记录调用者信息以及日志分割等。最后,文章演示了如何在gin框架中集成Zap,通过自定义中间件实现了日志记录和异常恢复功能。通过这些步骤,读者可以掌握Zap在实际项目中的应用与定制方法
go语言后端开发学习(四) —— 在go项目中使用Zap日志库
下一篇
云函数