文章思路来源:【优雅封装Go结构体构造函数】 https://www.bilibili.com/video/BV1ky4y1d7eT/?share_source=copy_web&vd_source=09bd1be8229f31f9b1fe4359ac7315ac

问题发掘

由于go语言中的函数是不支持重载的,导致我们不能直接去实现一个不同实现的构造函数。

例如我想去实现多个构造函数以实现我们在构造一个结构体实例时有多参数可选,如下我这样做是不被Go语言允许的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
type User struct {
Name string
Id int
Age int
}

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

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

解决方案

在Go语言中,支持传入的参数是个函数,于是我们可以想到,是否可以通过分开初始化的函数传入构造函数内,达到可选参数的作用呢?这为我们提供了解决的思路

由此,我们可以引入option模式

什么是Option模式

Option模式的专业术语为:Functional Options Pattern(函数式选项模式)

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

比如我们用option模式实现上面问题的需求:

User结构体

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
package object

type User struct {
Name string
Id int
Age int
}

type Option func(*User)

func NewUser(f ...Option) *User {
u := new(User)
for _, withVar := range f {
withVar(u)
}
return u
}

func WithName(name string) Option {
return func(u *User) {
u.Name = name
}
}

func WithID(id int) Option {
return func(u *User) {
u.Id = id
}
}

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

主函数

1
2
3
4
5
6
7
8
9
10
11
12
13
func main() {
u1 := object.NewUser(
object.WithName("IceWindy"),
object.WithID(114),
object.WithAge(18),
)
fmt.Println(u1) //output: &{IceWindy 114 18}

u2 := object.NewUser(
object.WithID(810),
)
fmt.Println(u2) //output: &{ 810 0}
}

分析代码

首先看到的是封装的函数类型

1
type Option func(*User)

这个函数类型是可选参数的提供的闭包的函数类型,将它封装起来可以使得我们程序具有更好的可读性。接下来我们看到可选参数的这一组代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
func WithName(name string) Option {
return func(u *User) {
u.Name = name
}
}

func WithID(id int) Option {
return func(u *User) {
u.Id = id
}
}

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

这组代码传入一个参数,然后返回一个闭包,这个闭包会去设置自己的User的字段,举个例子

  • 当我们调用一个参数,使用WithName("IceWindy")
  • 其返回值是一个func(u *User) { u.Name = "IceWindy" }
  • 该返回值会作为参数传入构造函数NewUser(f ...Option)内,将结构体实例的Name设置为"IceWindy"

接下来看到构造函数

1
2
3
4
5
6
7
func NewUser(f ...Option) *User { //使用可变参数接受多个Option函数
u := new(User) //得到一个空的结构体实例化地址
for _, withVar := range f { //使用for range从可变参数中依次执行Option函数
withVar(u)
}
return u
}

后记

感觉自己写的思路不是很清晰,网上没有看到比较好的、能理顺一下我的思路的文章,只好大概写成这样。自己大概是理解了,但是写出来有点困难,以后有空再来重构一下这篇文章。