1.结构体的实例化
type dbOptions struct {
Host string
Port int
UserName string
Password string
DBName string
}
func main() {
//这是最常见的结构体实例化方式
db := &dbOptions{
Host: "127.0.0.1",
Port: 3306,
UserName: "xhz",
Password: "root",
DBName: "mysql",
}
}
假设现在有一个
需求:
通过一个New函数,返回结构体实例化,不带参数会有默认值,带参数就对应更新默认值 如果不考虑设计模式的写法,那可能最好想的就是下面的方式:
unc NewDBclient(Host string, Port int, UserName string, Password string, DBName string) *dbOptions{
dbopts := &dbOptions{
Host: "127.0.0.1",
Port: 3306,
UserName: "xhz",
Password: "root",
DBName: "mysql",
}
if Host != "" {
dbopts.Host = Host
}
if Port != 0 {
dbopts.Port = Port
}
if UserName != "" {
dbopts.UserName = UserName
}
if dbopts.Password != "" {
dbopts.Password = Password
}
if dbopts.DBName != "" {
dbopts.DBName = DBName
}
return dbopts
}
//这种写法虽然可以满足需求,但是很明显代码显得十分累赘的感觉
//可以想象,如果一个结构体有几十个字段,那岂不是意味着几十个字段都要判断,而且传参都要传
//这么看来,这个方法还需改善一番
在开发过程中,其实不难发现很多第三方包会经常通过一个New函数来实例化出结构体
并且在 New 函数的入参会经常出现一些 withXXX( ) 的函数eg: NewXXXX(withXXX( ))
注意看 NewXXXX( )的参数就是一些函数选项,这种参数是函数选项的模式是 golang 特有的模式
也称之为 函数选项模式
2.函数选项模式
type dbOptions struct {
Host string
Port int
UserName string
Password string
DBName string
}
// Option 给函数类型取别名,从这里可以看出,函数的内部逻辑是没有限制的
type Option func(*dbOptions)
//函数选项模式的 New 函数
func NewDBclient(opt ...Option) *dbOptions {
//设置好默认的返回值
dbopts := &dbOptions{
Host: "127.0.0.1",
Port: 3306,
UserName: "xhz",
Password: "root",
DBName: "mysql",
}
//根据传入的一个个函数来更新返回值的字段
for _, v := range opt {
v(dbopts) //每一个函数选项都是接收一个结构体指针
}
return dbopts
}
到此 NewDBclient 函数就完成了,但是可以发现更新结构体字段是严格依靠 opt 的内部逻辑的
所以现在的任务是构造出一个 func(*dbOptions) 样子的函数即可eg:
type Option func(*dbOptions)
func withF1(db *dbOptions) {
db.Host = "1234567890"
}
上面是符合 func(*dbOptions)的样子了,但是很明显把 Host 写死了
所以,还需要把 Host 调整到入参的位置,类似于下面这样:
type Option func(*dbOptions)
func withF1(Host string){}
但是还需要满足func(*dbOptions)的样子要求,所以只能将返回值设计成func(*dbOptions)
type Option func(*dbOptions)
func withF2(Host string) Option {
return func(db *dbOptions) {
db.Host = Host
}
}
最终的代码
package main
import "fmt"
// 需求:通过一个New函数,可以返回自己想要的结构体指针,并且不带参数也会有默认值,有参数就对应改
type dbOptions struct {
Host string
Port int
UserName string
Password string
DBName string
}
// Option 给函数取别名,从这里可以看出,函数的内部逻辑是没有限制的
type Option func(*dbOptions)
func NewDBclient(opt ...Option) *dbOptions {
dbopts := &dbOptions{
Host: "127.0.0.1",
Port: 3306,
UserName: "xhz",
Password: "root",
DBName: "mysql",
}
for _, v := range opt {
v(dbopts) //每一个函数选项都是接收一个结构体指针
}
return dbopts
}
// withHost 的返回值恰好是 NewDBclient 的入参
func withHost(host string) Option {
return func(o *dbOptions) {
o.Host = host
}
}
func main() {
re := NewDBclient(withHost("localhost"))
fmt.Printf("%#v\n", re)
}
例子中的 withHost( ) 函数只是展示了更新 Host 一个字段的值,所以完全可以根据需求定义其他的with函数,eg:withHost( ) withPort( ) withUserNameAndPassword( )...