Golang 选项模式

需求

Golang不支持在函数定义的时候声明默认值,也就是说我们不能够使用类似下面的代码

1
2
3
func New(addr string="localhost",  port string="8080"){
...
}

并且也不支持函数的重载,函数名不能够重复,无法根据需要的参数数量创建不同的函数形式,也就是我们不能使用下面的方式

1
2
3
4
5
6
7
func New(addr string){
...
}
// 再次使用同样的函数名会报错
func New(addr string, port string){
...
}

返回默认对象

那么我们如何才能设置默认值呢?一种很自然的方式比如说,我们创建对象的时候就返回一个带有默认值的对象

1
2
3
4
5
6
7
8
9
10
11
12
type myStruct struct {
Host string
Port string
}

func New() *myStruct{
// 返回一个带有默认值的对象
return &myStruct{
Host: "0.0.0.0",
Port: "8080",
}
}

然后我们如果需要修改参数,那么我们可以手动去修改

1
2
3
config := New()
// 手动修改
config.Port = "8000"

上面的这种方式其实已经可以解决我们的需求,但是还是不够好,因为这种方式会直接暴露结构体中的字段,结构体中的字段名首字母必须大写,并且用户进行修改的时候会直接对字段进行修改,无法对设置的参数进行校验。

Option模式

下面我们主要讲解 Option模式,也就是 函数选项模式,这种模式下在我们设置默认值的同时支持检验参数。

Option 模式为 Golang 的开发者提供了将一个函数的参数设置为可选的功能,也就是说我们可以选择参数中的某几个,并且可以按任意顺序传入参数。

下面我们来看看具体如何使用:

首先,我们需要设置一个结构体包括我们需要设置的一些参数

1
2
3
4
type Config struct{
Addr string
Host string
}

然后,我们需要定义一个Option函数类型可以修改我们的参数结构体

1
type Option func(c *Config)

对于我们需要进行修改的参数,我们可以声明对应的函数来修改参数值,这里利用了闭包的特性。

1
2
3
4
5
6
7
8
9
10
11
func WithHost(host string) Option {
return func(c *Config) {
c.Host = host
}
}

func WithPort(port string) Option {
return func(c *Config) {
c.Port = port
}
}

接着,我们需要设置一个默认值,如下,也就是用户没有设置任何参数的时候所得到的值

1
2
3
4
var defaultConfig = Config{
Host: "0.0.0.0",
Port: "8080",
}

最后,提供一个函数接口,创建对象的时候,用户可以执行一系列的Option来修改默认的参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
type myStruct struct {
host string
port string
}

func New(opts ...Option) *myStruct {
// 默认情况下使用默认参数
config := defaultConfig

for _, opt := range opts {
// opt 就是一个函数,以 *Config为参数,能够修改其中的内容
opt(&config)
}

return &myStruct{
host: config.Host,
port: config.Port,
}
}

这里还有另外一种方式实现,思路差不多,直接上代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
type myStruct struct {
host string
port string
}

// 设置一个接口类型
type Option interface {
apply(*myStruct)
}

// 声明一个类型,以需要修改的结构体为参数
type optinFunc func(*myStruct)

// 实现Option接口
func (f optinFunc) apply(m *myStruct) {
f(m)
}

// 修改参数
func WithHost(host string) Option {
return optinFunc(func(ms *myStruct) {
ms.host = host
})
}

func WithPort(port string) Option {
return optinFunc(func(ms *myStruct) {
ms.port = port
})
}

var defaultOne = myStruct{
host: "0.0.0.0",
port: "8080",
}

// 创建一个新的对象
func New(options ...Option) *myStruct {
m := defaultOne
for _, opt := range options {
opt.apply(&m)
}
return &m
}

// 可以认为我们创建的WithHost, WithPort
// 就是一个可以修改struct字段的函数
// 一般我们会暴露这些函数给使用
// 但是在包内部我们可以修改结构体的私有成员

测试结果

1
2
3
4
5
6
7
fmt.Printf("%#v\n", New())
fmt.Printf("%#v\n", New(WithHost("127.0.0.1")))
fmt.Printf("%#v\n", New(WithHost("127.0.0.1"), WithPort("8000")))
// 输出
// &main.myStruct{host:"0.0.0.0", port:"8080"}
// &main.myStruct{host:"127.0.0.1", port:"8080"}
// &main.myStruct{host:"127.0.0.1", port:"8000"}

虽然说,Option模式在设置参数的时候特别有用,而且显得十分优雅,但是通过对它的实现,我们也知道使用起来相对而言还是比较麻烦,需要添加很多函数


生活杂笔,学习杂记,偶尔随便写写东西。

作者

Edgar

发布于

2021-07-13

更新于

2021-12-21

许可协议

评论