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

考虑到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"
)

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()