一个活跃在众多 Go 项目中的编程模式

开发 前端
今天我们介绍一个在 Go 语言中非常流行的编程模式:函数式选项模式(Functional Options)。该模式解决的问题是,如何更动态灵活地为对象配置参数。可能读者不太明白该痛点,不急,我们将在下文详细详解。

今天我们介绍一个在 Go 语言中非常流行的编程模式:函数式选项模式(Functional Options)。该模式解决的问题是,如何更动态灵活地为对象配置参数。可能读者不太明白该痛点,不急,我们将在下文详细详解。

问题

假设我们在代码中定义了一个用户的结构体对象 User,它拥有以下属性。

type User struct {
ID string // 必需项
Name string // 必需项
Age int // 非必需项
Gender bool // 非必需项
}

初始化该对象时,最简单的方式是直接填充属性值,例如

u := &User{ID: "12glkui234d", Name: "菜刀", Age: 18, Gender: true}

但是这里存在一个问题:User 对象中的属性并不一定都是可导出的,例如 User 有一个属性字段为 password(首字母小写,非导出),如果在其他模块中需要构造 User 对象,这样就不能填充该 password 字段了。

所以我们需要定义构造 User 对象的函数,首先能想到最简单的构造函数方式如下。

func NewUser(id, name string, age int, gender bool) *User {
return &User{
ID: id,
Name: name,
Age: age,
Gender: gender,
}
}

但是这样也存在一些问题:对于 User 对象而言,只有 ID、Name 属性是必须的,Age 与 Gender 为非必需项,且并不能设置默认值,例如 Age 的默认值为 0,Gender 的默认值是 false ,这显然不太合理。

面对该问题,我们可以采用的解决方案有哪些呢?

方案一:多函数构造

我们能想到最粗暴地解决方法是:为每种参数情况设置一种构造函数。如下代码所示

func NewUser(id, name string) *User {
return &User{ID: id, Name: name}
}

func NewUserWithAge(id, name string, age int) *User {
return &User{ID: id, Name: name, Age: age}
}

func NewUserWithGender(id, name string, gender bool) *User {
return &User{ID: id, Name: name, Gender: gender}
}

func NewUserWithAgeGender(id, name string, age int, gender bool) *User {
return &User{ID: id, Name: name, Age: age, Gender: gender}
}

这种方式适合参数较少且不易发生变化的情况。该方式在 Go 标准库中也有使用,例如 net 包中的 Dial 和 DialTimeout 方法。

func Dial(network, address string) (Conn, error) {}
func DialTimeout(network, address string, timeout time.Duration) (Conn, error) {}

但该方式的缺陷也很明显:试想,如果构造对象 User 增加了参数字段 Phone,那么我们需要新增多少个组合函数?

方案二:配置化

另外一种常见的方式是配置化,我们将所有可选的参数放入一个 Config 的配置结构体中。

type User struct {
ID string
Name string
Cfg *Config
}

type Config struct {
Age int
Gender bool
}

func NewUser(id, name string, cfg *Config) *User {
return &User{ID: id, Name: name, Cfg: cfg}
}

这样,我们只需要一个 NewUser() 函数,不管之后增加多少配置选项,NewUser 函数都不会得到破坏。

但是,这种方式,我们需要先构造 Config 对象,这时候对 Config 的构造又回到了方案一中存在的问题。

方案三:函数式选项模式

面对这样的问题,我们还可以选择函数式选项模式。

首先,我们定义一个 Option 函数类型

type Option func(*User)

然后,为每个属性值定义一个返回 Option 函数的函数

func WithAge(age int) Option {
return func(u *User) {
u.Age = age
}
}

func WithGender(gender bool) Option {
return func(u *User) {
u.Gender = gender
}
}

此时,我们将 User 对象的构造函数改为如下所示

func NewUser(id, name string, options ...Option) *User {
u := &User{ID: id, Name: name}
for _, option := range options {
option(u)
}
return u
}

按照这种构造方式,我们就可以这样配置 User 对象了

u := NewUser("12glkui234d", "菜刀", WithAge(18), WithGender(true))

以后不管 User 增加任何参数 XXX,我们只需要增加对应的 WithXXX 函数即可,是不是非常地优雅?

Functional Options 这种编程模式,我们经常能在各种项目中找到它的身影。例如,我在 tidb 项目中仅使用 opts ... 关键字搜索,就能看到这么多使用了 Functional Options 的代码(截图还未包括全部)。

总结

函数式选项模式解决了如何动态灵活地为对象配置参数的问题, 但是需要在合适的场景才使用它。

当对象的配置参数复杂,例如可选参数多、非导入字段、参数可能随版本增加等情况,这时函数式选项模式就可以很好地帮助到我们。


责任编辑:武晓燕 来源: Golang技术分享
相关推荐

2015-08-03 11:45:37

storyboard

2023-09-14 08:46:50

ReactVue

2022-04-27 05:55:43

去QA化自动化测试开发

2021-03-07 22:37:17

Go代码模式

2017-03-20 18:03:51

2021-06-02 08:00:57

WebAsyncTas项目异步

2017-11-14 11:12:50

Go语言编译器

2024-03-04 00:00:00

GolangNode.js性能

2015-10-12 15:50:07

PaaS云平台开发go

2020-06-28 08:10:00

GoGOSSAFUNC图编程语言

2023-10-12 14:22:45

2024-01-02 10:46:14

2018-04-27 16:00:15

Windows上帝模式

2021-11-28 22:33:01

Go选项模式

2022-05-22 13:55:30

Go 语言

2023-02-26 01:37:57

goORM代码

2020-06-03 10:09:03

微服务项目版本号

2016-01-21 18:03:02

开源社区指标开源

2021-11-04 10:29:01

CSS前端

2022-04-24 15:29:17

微服务go
点赞
收藏

51CTO技术栈公众号