EAVE

Go 创建对象时,如何优雅的传递初始化参数

Go 并不像c++和python那样,支撑函数默许参数。所以运用Go时,咱们需求一种便利、通用的办法来完结这件事。

Go的许多开源项目都运用Option方式,但各自的完成可能有少许细微差别。

本文将经过一个渐进式的demo示例来介绍Option方式,以及相关的一些考虑。本文将内容切分为 10 个小模块,假如觉得前面的衬托冗余,想直接看 Option 方式的介绍,能够从小标题七开端阅览。

先看demo,一开端咱们的代码是这样的:

type Foo struct {
num int
str string
// ...
}
func New *Foo {
t// ...
treturn Foo{
num: num,
str: str,
}
}
// ...













咱们有一个Foo结构体,内部有num和str两个特点,New函数传入两个初始化参数,结构一个Foo目标。

ok,一切都满足简略。

假定咱们需求对Foo内部添加两个特点,一起结构函数也需求支撑传入这两个新增特点的初始值。有一种修正办法是这样的:

func New

能够看到,这种办法,跟着初始化参数个数、类型的改动,咱们New函数的函数签名也需随之改动。这带来两个害处:

有一种坚持兼容性的处理计划,是保存之前的New函数,再创立一个新的结构函数,比方New2,用于完成 4 个参数的结构办法。

这种处理计划在大部分时分会导致代码可维护性下降。

另一种处理计划,是把一切的参数都放入一个结构体中。就像这样:

type Foo struct {
option Option
t// ...
}
type Option struct {
num int
str string
}
func New *Foo {
t// ...
treturn Foo{
option: option,
}
}














这种办法,处理了上面提出的两个问题。可是,假定咱们想为参数供给默许参数呢?

比方说当调用方不设置num时,咱们期望它的默许值是100;不设置str时,默许值为hello。

// 结构目标时只设置 str,不设置 num
foo := New


这种做法可行的条件是,特点的默许值也为0值。

假定咱们期望option.num特点默许值是100,那么当内部接纳到的option.num为0时,咱们无法区别是调用方期望将option.num设置为0,仍是调用方压根就没设置option.num。然后导致咱们不知道将内部的option.num设置为0好,仍是坚持默许值100好。

事实上,这个问题不仅仅是传递Option时才会呈现,即便一切参数都运用最上面那种直接传递的办法,也会存在这个问题,即0值无法作为外部是否设置的判别条件。

有一种处理计划,是运用*Option即指针类型作为初始化参数,假如外部传入为nil,则运用默许参数。代码如下:

func New *Foo {
tif option == nil {
tt// 外部没有设置参数
}
}





该计划存在的问题是,一切的参数要么悉数由外部传入,要么悉数运用默许值。

怎么才干细化到每一个详细的参数,外部设置了运用外部设置的值,外部没有设置则运用默许值呢?

一种处理计划,是Option中的一切特点,都运用指针类型,假如特定参数为nil,则该参数运用默许参数。代码如下:

type Option struct {
num *int
str *string
}
func New *Foo {
tif option.num == nil {
tt// num 运用默许值
} else {
tt// option.num 即为调用方设置的初始值
}
t// ...
}












该计划存在的问题是,关于调用方来说,运用起来有些反人类,由于你无法运用相似 1的写法对一个整型字面常量取地址,这意味着调用方有必要分外界说一个变量保存他需求设置的参数的值,然后再对这个变量取地址赋值给 Option 的特点。代码如下:

// // 下面这种写法会形成编译过错
// option := {
// num: 200,
// str: "world",
// }
//
// // 只能这样写
// num := 200
// str := "world"
// option := {
// num: num,
// str: str,
// }
// foo := New














看起来有点,额,不太高雅。

另一种值得一提的处理计划,是运用Go可变参数的特性。代码如下:

func New {
if len == 0 {
tt// 调用方没有设置 num2,内部的 num2 应运用默许值
} else {
t// num2[0] 即为调用方设置的初始值
}
}







该计划存在的问题是,只能有一个参数有默许值。

ok,说了这么多,是时分开端上主菜了。Go是支撑头号函数的言语,即能够将函数作为变量传递。所以咱们能够像下面这样写:

type Option struct {
num int
str string
}
type ModOption func
func New *Foo {
t// 默许值
option := Option{
num: 100,
str: "hello",
}
modOption
treturn Foo{
option: option,
}
}
















咱们的New函数不再直接接纳Option的值,而是供给了一种相似于钩子函数的功用,使得在内部对option设置完默许值之后,调用方能够直接挑选修正哪些特点。比方调用方只设置num,代码如下:

New {
t// 调用方只设置 num
option.num = 200
})




那么假定有些时分,咱们觉得某个参数是调用方有必要关怀的,不该该由内部设置默许值呢?咱们能够这样写:

package main
type Foo struct {
key string
option Option
t// ...
}
type Option struct {
num int
str string
}
type ModOption func
func New *Foo {
option := Option{
num: 100,
str: "hello",
}
modOption
return Foo{
key: key,
option: option,
}
}
// ...
func main {
New {
tt// 调用方只设置 num
option.num = 200
})
}





























最终再来一种常见的、高档点的写法。在上面代码的根底上,添加如下代码:

func WithNum ModOption {
return func {
option.num = num
}
}
func WithStr ModOption {
treturn func {
option.str = str
}
}










然后是调用方的代码:

// 能够这样写
foo := New)
// 还能够这样写
foo := New)




能不能两个一重用呢?其实是能够的,结合咱们上文讲到的可变参数,将New函数修正如下:

func New *Foo {
option := Option{
num: 100,
str: "hello",
}
tfor _, fn := range modOptions {
fn
}
treturn Foo{
key: key,
option: option,
}
}













然后是运用方的代码:

New, WithStr)

至此,关于Option方式的介绍就完毕啦。

事实上,Option方式除了在创立目标时能够运用,里边的一些API规划思维,Go的小技巧,在编写一般函数时也能够运用。

方式说白了便是一种套路。在完成功用的根底之上,我们都了解了某种固有套路的写法,都按着这个套路走,那么代码的可读性、可维护性就更高些。

关于一个特定场景,没有最好的方式,只要最适合的方式。不要过度规划,手里就一把锤子,瞅啥都是钉子。

举个比方,最终说的那种WithXXX写法,我个人认为在大部分时分都有点皮裤套棉裤,简略工作复杂化的感觉,不如只用一个ModOption直接修正option来得简略、直观,所以我简直不必WithXXX的写法。可是在有些场景,你假如觉得供给WithXXX对调用方更友爱,那么用用也挺好。

为了坚持场景的朴实性,上面的demo可能会有些笼统。假如你想进一步看看Option方式在实践项目中是怎么运用的,能够看看我的这个开源项目:naza[1]。该项目在结构目标时很多运用了Option方式。比方 consistenthash.go[2], bitrate.go[3] 等等。而且做了一些私人化的风格标准。

最终,感谢阅览,假如觉得文章还不错,能够给我的github项目naza[4] 来个star哈。该项目是我学习Go时写的一些轮子代码调集,后续我还会写一些文章逐一介绍里边的轮子以及一些写Go代码的技巧。

naza项目地址:https://github.com/q191201771/naza

naza 的其他的文章:

原文链接: https://pengrl.com/p/60015/

原文出处:yoko blog[5]

原文作者:yoko[6]

版权声明: 本文欢迎任何方式转载,转载时完好保存本声明信息即可。本文后续一切修正都会第一时间在原始地址更新。

参考资料

[1]naza: https://github.com/q191201771/naza

[2]consistenthash.go: https://github.com/q191201771/naza/blob/master/pkg/consistenthash/consistenthash.go

[3]bitrate.go: https://github.com/q191201771/naza/blob/master/pkg/bitrate/bitrate.go

[4]naza: https://github.com/q191201771/naza

[5]yoko blog: https://pengrl.com

[6]yoko: https://github.com/q191201771

喜爱本文的朋友,欢迎重视“Go言语中文网”:

Go言语中文网启用微信学习沟通群,欢迎加微信:274768166