Go 如何禁止直接结构初始化
考虑到Go中的以下软件包,是否可以防止使用Go 如何禁止直接结构初始化,go,Go,考虑到Go中的以下软件包,是否可以防止使用Bar{..}直接初始化Bar,而不从软件包中反暴露Bar 包装条: package bar import () type Bar struct { A string B string } func NewBar(baz string) Bar{ return Bar{A:baz, B:baz+baz} } 包main: package main import ( "fmt" "./bar" ) fu
Bar{..}
直接初始化Bar
,而不从软件包中反暴露Bar
包装条
:
package bar
import ()
type Bar struct {
A string
B string
}
func NewBar(baz string) Bar{
return Bar{A:baz, B:baz+baz}
}
包main
:
package main
import (
"fmt"
"./bar"
)
func main() {
x := bar.NewBar("sad") //all bars should be created with this
y := bar.Bar{A: "fadss"} //and this should be disallowed
bzzBar(x)
bzzBar(y)
}
func bzzBar(bzz bar.Bar) { //but I can't do 'Bar -> bar' because I want to use the type
fmt.Println(bzz)
}
我的直觉告诉我这是不可能的,所以这也是一个有效的答案。如果您提供
String()
函数,您应该能够不导出a
和B
:
type Bar struct {
a string
b string
}
func NewBar(baz string) Bar{
return Bar{a:baz, b:baz+baz}
}
func (Bar) String() string {
return a + " " b
}
您可以使所有
Bar
字段未报告,并为它们提供getter和setter。这样,软件包用户仍然可以做一些愚蠢的事情,比如
a := Bar{}
b := Bar{"foo"}
这两种方法都没有,或者看起来都很有用(虽然前者可以用来创建一个空的
条
,类似于&bytes.Buffer{}
。没有办法阻止条{}
或条{A:“foo”}
要按所需方式控制结构,可以返回接口,而不是导出结构本身
举例如下:
package bar
type Bar interface{
A() string
B() string
// if you need setters
SetA(string)
SetB(string)
}
type bar struct {
a string
b string
}
func (b *bar) A() string { return b.a }
func (b *bar) B() string { return b.b }
func (b *bar) SetA(val string) { b.a = val }
func (b *bar) SetB(val string) { b.b = val }
func NewBar(baz string) Bar {
return &bar{a:baz, b:baz+baz}
}
Go标准库中使用的习惯用法是:
包装条
package bar
import (
"fmt"
)
type Bar struct {
a string
b string
}
func New(baz string) *Bar {
return &Bar{a: baz, b: baz + baz}
}
func (b *Bar) BzzBar() {
fmt.Println(*b)
}
package main
package main
import (
"bar"
)
func main() {
x := bar.New("sad") //all bars should be created with this
x.BzzBar()
// error: unknown bar.Bar field 'A' in struct literal
// y := bar.Bar{A: "fadss"} //and this should be disallowed
}
输出:
{sad sadsad}
{true sad sadsad}
增编:
当分配内存来存储值时,通过
声明或调用make或new,并且没有显式初始化
如果提供,则为内存提供默认初始化。每个
该值的元素的类型设置为零值:false
对于布尔型,0表示整数,0.0表示浮点数,0.0表示字符串,0.0表示字符串
用于指针、函数、接口、切片、通道和映射。这
初始化是递归完成的,例如
如果未指定值,结构数组的字段将为零
Go标准库中使用的另一个习惯用法是使零值有意义。例如,如果未显式初始化new
,则默认值为false
type Bar struct {
new bool
a string
b string
}
比如说,
package bar
import (
"fmt"
)
type Bar struct {
new bool
a string
b string
}
func New(baz string) *Bar {
return &Bar{new: true, a: baz, b: baz + baz}
}
func (b *Bar) notnew() {
if b == nil || !b.new {
panic("bar.Bar not bar.New")
}
}
func (b *Bar) Bzz() {
b.notnew()
fmt.Println(*b)
}
输出:
{sad sadsad}
{true sad sadsad}
虽然提供的所有答案都是合理的,但我要补充一点:不要把包的用户当作白痴。如果您有一种文档化的方法(通过
NewBar()
)函数实例化Bar
s,只需保留它,并将其视为您和用户之间的契约。否则,很难在某个地方划清界限:毕竟,使用safe
和reflect
我可以对任何Bar
实例做任何我想做的事情,不管它是如何实例化的。在这种情况下,我就是白痴:)。我希望在编译时捕获这个问题的实例,这样我就不会在运行时遇到致命错误。是的,我知道您总是可以访问该类型,但随后您明确地对自己执行了该操作,而执行Bar{..}
应该创建一个有效的Bar或break。虽然我认为这是正确的,但这个问题也是关于可用性方面正确的包设计:作为一个用户,我会发现在包中出现致命错误是非常令人沮丧的,因为我错误地实例化了一个类型。我宁愿没有权限访问某个类型,然后被允许错误地实例化它。我的直觉(似乎得到@VonC答案的支持)是“如果一个字段必须有一个特定的值,用户不能直接使用它,不要导出它(并且可能会提供一个访问器)”。当然,这在所有情况下都不容易做到。关于私有字段的初始化,您还可以考虑:无用的接口不是惯用的,这不是java,您应该只信任包的用户。如果只是代码的更改需要现在新的NewBar
而不是Bar{}
,那么您可以使用编辑器工具来查找和修复这些情况。@Wessie不要让您对getter/和setter的盲目仇恨妨碍了这里。相信我,我和你一样不喜欢,但这是对用户隐藏结构初始化的惯用方法。应该尽可能少地使用它,但是如果用户不能用Bar{..}
习惯用法创建有意义的类型,那么它非常有用,因此您不会让他/她接触到它。@RickyA不,因为我不相信从一开始就隐藏结构初始化的要求。标准库as也是一个很好的例子。虽然这种方法很有效,并且得到了语言特性的支持,但这种方法在标准库中没有使用,因此不是惯用的。为了不卷入关于什么是惯用的与否的宗教战争,我删除了“惯用”短语。仍然觉得这比彼得森的“老套”的“惯用”答案更恰当;是的,这是标准的围棋成语;是的,它在隐藏字段方面起作用;但是y:=Bar{}
仍然快乐地飞着。我想真正的答案是:接受它。@RickyA:那我们就让b:=bar.bar{}
在使用时崩溃吧。请参阅我修改后的答案。作为标准的go习惯用法并显示了导致此失败的方法,您得到了被接受的答案。@peterSO我发现您的附录不好,因为它在初始化时没有失败,但在调用方法时失败。这可能是在远离初始化的代码处,使其难以修复。您还必须在导出的每个*Bar方法中调用b.notnew()
。