由于Golang不支持函数重载,因此无法像C++或Java那样可以定义多个同名不同参的构造函数,因此如果要定义多个构造函数就需要使用不同的名称。但是如果后续要给结构体增加新的字段,那么可能就需要修改很多的构造函数,函数选项模式就可以完美解决这个问题。
1、正常的构造函数
拿下面的代码举个例子:
type User struct {
username string
password string
nickname string
age uint8
gender bool
address string
email string
}
func NewUser(username, password, nickname, address, email string, age uint8, gender bool) *User {
return &User {
username: username,
...
}
}
// 假设有时候只想填username和password,如果使用上一个构造函数就需要这样写:
u := NewUser("zhangsan", "fawaikuangtu", "", "", "", 0, false)
// 非常的麻烦
// 这时候可以新增一个构造函数
func NewUserSimple(username, password string) *User {
return &User {
username: username,
password: password,
}
}
// 如果有别的需要可能要增加更多的构造函数,也是挺麻烦的
// 当我们在开发过程中,需要给User新增一个子段,假设为手机号,那么我们就需要修改User结构体,以及其一些构造函数,如果构造函数比较多,就可能需要修改大量的构造函数
// 也许你想到可以给其添加getter和setter:
type User struct {
username string
password string
nickname string
age uint8
gender bool
address string
email string
phoneNum string
}
func (u *User) GetPhoneNum() string {
return u.phoneNum
}
func (u *User) SetPhoneNum(pnum string) {
u.phoneNum = pnum
}
// 然后在创建对象的时候可以这样
u := NewUser("zhangsan", "fawaikuangtu", "", "", "", 0, false)
u.SetPhoneNum("911")
// 是不是看起来挺奇葩的,其他的参数都可以通过构造函数来设置,而phoneNum要单独使用setter来设置
// 这样不符合go语言的简洁性,那还不如直接将电话号码首字母大写设为可导出的直接赋值来的快:
u := NewUser("zhangsan", "fawaikuangtu", "", "", "", 0, false)
u.PhoneNum = "911"
// 而且Go语言不像其他语言,对于getter和setter使用的很少。我感觉使用这个也是多此一举
2、函数选项模式
接下来使用函数选项模式来实现构造函数:
type User struct {
username string
password string
nickname string
age uint8
gender bool
address string
email string
}
type OptionFunc func(u *User)
// 假设username和password是必须要填的选项,那么构造函数就可以这样写
func NewUser(username, password string, options ...OptionFunc) *User {
u := &User{
username: username,
password: password,
}
for _, o := range options {
o(u)
}
// 也可以在这设置一些默认值
if u.age == 0 {
u.age = 18
}
...
return u
}
// 然后在下面提供一系列的设置值的With函数
func WithNickname(nickname string) OptionFunc {
return func(u *User) {
u.nickname = nickname
}
}
func WithAge(age uint8) OptionFunc {
return func(u *User) {
u.age = age
}
}
...
func WithEmail(email string) OptionFunc {
return func(u *User) {
u.email = email
}
}
// 在创建对象的时候就可以这样写了
u := NewUser("zhangsan", "fawaikuangtu", WithNickname("luoxiang"), WithEmail("kuangtu@qq.com"))
// 当我们新增加了一个手机号的字段时,只需要在结构体中新增字段以及新增一个WithPhoneNum的函数即可
当然,也有一些其他的方式,比如下面的方式:
type User struct {
username string
password string
nickname string
age uint8
gender bool
address string
email string
}
func NewUser(username, password string) *User {
return &User{
username: username,
password: password,
}
}
func (u *User) Nickname(nickname string) *User {
u.nickname = nickname
return u
}
...
func (u *User) Address(addr string) *User {
u.address = addr
return u
}
func (u *User) Email(email string) *User {
u.email = email
return u
}
// 在创建对象时这样来
u := NewUser("zhangsan", "fawaikuangtu").Nickname("luoxiang").Email("kuangtu@qq.com")
// 这种方式跟函数选项模式有点相似,不同的是这个方式需要给对应的结构体绑定方法,这也是个不太好的地方。
总结:函数选项模式在一些场景下可以很好地解决golang不支持函数重载的问题,但是在一些比较简单的场景下,比如只有两三个甚至一个构造函数的情况下,完全没有必要使用函数选项模式,直接写不同名的构造函数即可。但是在参数复杂同时一些参数又会有默认值的情况下,就可以使用函数选项模式,在很多的开源库中,函数选项模式也有使用,在大多数场景下用于一些基础的配置,比如Mysql、Redis等。
函数选项模式的优点
:
- 支持任意顺序的参数传递
- 易于扩展,新增字段时,只需新增一个函数即可
- 支持默认值的设置
缺点
:
- 需要很多选项函数,增大代码量