Go中的构造函数

Mar*_*ace 156 oop constructor go

我有一个结构,我希望它用一些合理的默认值初始化.

通常,这里要做的是使用构造函数,但由于go不是传统意义上的OOP,因此它们不是真正的对象,也没有构造函数.

我注意到了init方法,但是在包级别.是否有类似的东西可以在结构级别使用?

如果不是在Go中这种类型的东西被接受的最佳实践是什么?

Den*_*ret 177

当零值不能产生合理的默认值时,或者某些参数对于struct初始化是必要的时,有一些构造函数的等价物.

假设你有这样的结构:

type Thing struct {
    Name  string
    Num   int
}
Run Code Online (Sandbox Code Playgroud)

那么,如果零值不合适,通常会构造一个带有NewThing返回指针的函数的实例:

func NewThing(someParameter string) *Thing {
    p := new(Thing)
    p.Name = someParameter
    p.Num = 33 // <- a very sensible default value
    return p
}
Run Code Online (Sandbox Code Playgroud)

当你的结构很简单时,你可以使用这个压缩的结构:

func NewThing(someParameter string) *Thing {
    return &Thing{someParameter, 33}
}
Run Code Online (Sandbox Code Playgroud)

如果你不想返回指针,那么练习是调用函数makeThing而不是NewThing:

func makeThing(name string) Thing {
    return Thing{name, 33}
}
Run Code Online (Sandbox Code Playgroud)

参考:在Effective Go中使用new进行分配.

  • 使用`new`分配结构并在之后设置值并不常见.结构文字是那里的首选方式.我也不确定你的"makeThing"命名约定.标准库一致地调用构造函数New()或NewThing(),我从未遇到任何makeThing()函数... (8认同)
  • 是的,但是“Effective Go”的以下段落介绍了结构文字并演示了如何使用它们来编写更惯用的“NewFile”构造函数版本:) (4认同)
  • 好的,这很有意义,但这意味着其中的客户必须了解New和make函数。即这不是所有结构中的标准。我想这可以通过接口来处理 (3认同)
  • 我不确定你的意思.具有NewThing功能是标准的.如果你的意思是他们没有被自动调用,是的,但你无论如何都不能自动使用结构.我不认为您应该尝试使用接口隐藏这些构造函数,代码在出现时更清晰. (2认同)
  • 我情不自禁,但这违背了封装的原则。在包含多个“类”的数据包中(这就是结构 + 方法之类的概念,无论您如何安排它),不应再处理命名冲突。但是在这种荣耀 Go 方法中,您现在基本上不知道哪个函数将构造函数角色作为工厂。所以你最终会得到像“NewClass”这样的命名约定。Go 是糟糕的编码风格。 (2认同)

Vol*_*ker 123

实际上有两种被接受的最佳实践:

  1. 使结构的零值成为合理的默认值.(虽然这对于大多数来自"传统"oop的人来说看起来很奇怪但它经常起作用并且非常方便).
  2. 提供一个函数,func New() YourTyp或者如果在函数包中有多个这样的类型func NewYourType1() YourType1,等等.

记录你的类型的零值是否可用(在这种情况下,它必须由其中一个New...函数设置.)对于"传统主义"oops:没有阅读文档的人将无法使用你的类型正确,即使他不能在未定义的状态下创建对象.)

  • 这将如何适用于地图等属性。这个默认值是零,对吧?因此,这些是否应该始终通过 New 函数进行初始化? (3认同)
  • 是的,不,这取决于.最可能的是,提供一个`func New()T`.但根据具体情况,你可以检查这个零地图,并在需要时只检查`make`.在这种情况下:如果此映射创建对于并发使用是安全的(也就是使您的映射受到例如互斥锁保护的代码),请记录.取决于地图是否导出...很难说没有看到代码. (2认同)

zzz*_*zzz 35

Go有对象.对象可以有构造函数(尽管不是自动构造函数).最后,Go是一种OOP语言(数据类型附带了方法,但无可否认,OOP的定义是无穷无尽的.)

尽管如此,公认的最佳实践是为您的类型编写零个或多个构造函数.

由于@dystroy在我完成这个答案之前发布了他的答案,让我只添加他的示例构造函数的替代版本,我可能会将其编写为:

func NewThing(someParameter string) *Thing {
    return &Thing{someParameter, 33} // <- 33: a very sensible default value
}
Run Code Online (Sandbox Code Playgroud)

我想向您展示此版本的原因是,通常可以使用"内联"文字而不是"构造函数"调用.

a := NewThing("foo")
b := &Thing{"foo", 33}
Run Code Online (Sandbox Code Playgroud)

现在*a == *b.

  • @lazywei a!= b,但*a ==*b因为它们指向的结构具有相等的字段,请参阅http://play.golang.org/p/A3ed7wNVVA作为示例 (5认同)
  • +1因为平等的事情.它可能有点(或完全)偏离主题,但它很重要.我认为它与Go1一起出现,不是吗? (4认同)
  • 你能补充一下为什么a和b相等吗? (4认同)

Seb*_*tos 10

Go中没有默认构造函数,但您可以为任何类型声明方法.您可以习惯于声明一个名为"Init"的方法.不确定这是否与最佳实践有关,但它有助于保持名称简短而不会失去清晰度.

package main

import "fmt"

type Thing struct {
    Name string
    Num int
}

func (t *Thing) Init(name string, num int) {
    t.Name = name
    t.Num = num
}

func main() {
    t := new(Thing)
    t.Init("Hello", 5)
    fmt.Printf("%s: %d\n", t.Name, t.Num)
}
Run Code Online (Sandbox Code Playgroud)

结果是:

Hello: 5
Run Code Online (Sandbox Code Playgroud)

  • 问题:从语义上讲, `t := new(Thing) \n t.Init(...)` 与 `var t Thing \n t.Init(...)` 相同,对吧?哪种形式在 Go 中被认为更惯用? (3认同)

Liu*_*hyn 10

在 Go 中,可以使用返回指向已修改结构的指针的函数来实现构造函数。

type Colors struct {
    R   byte
    G   byte
    B   byte
}

// Constructor
func NewColors (r, g, b byte) *Colors {
    return &Color{R:r, G:g, B:b}
}
Run Code Online (Sandbox Code Playgroud)

为了弱依赖和更好的抽象,构造函数不返回指向结构的指针,而是返回该结构实现的接口。

type Painter interface {
    paintMethod1() byte
    paintMethod2(byte) byte
}

type Colors struct {
    R byte
    G byte
    B byte
}

// Constructor return intreface
func NewColors(r, g, b byte) Painter {
    return &Color{R: r, G: g, B: b}
}

func (c *Colors) paintMethod1() byte {
    return c.R
}

func (c *Colors) paintMethod2(b byte) byte {
    return c.B = b
}
Run Code Online (Sandbox Code Playgroud)

  • 我不认为返回接口是最佳实践。您通常想要接受一个接口并返回一个指向结构的指针(可能实现一个接口)。它仍然是可以测试的。调用代码必须将返回值视为接口类型。这样可以透明地将模拟分配给它。 (8认同)

Iva*_*cki 8

我喜欢这篇博客文章中的解释:

New函数是Go约定,用于创建创建供应用程序开发人员使用的核心类型或其他类型的包的约定。查看如何在log.go,bufio.go和cypto.go中定义和实现New:

log.go

// New creates a new Logger. The out variable sets the
// destination to which log data will be written.
// The prefix appears at the beginning of each generated log line.
// The flag argument defines the logging properties.
func New(out io.Writer, prefix string, flag int) * Logger {
    return &Logger{out: out, prefix: prefix, flag: flag}
}
Run Code Online (Sandbox Code Playgroud)

bufio.go

// NewReader returns a new Reader whose buffer has the default size.
func NewReader(rd io.Reader) * Reader {
    return NewReaderSize(rd, defaultBufSize)
}
Run Code Online (Sandbox Code Playgroud)

crypto.go

// New returns a new hash.Hash calculating the given hash function. New panics
// if the hash function is not linked into the binary.
func (h Hash) New() hash.Hash {
    if h > 0 && h < maxHash {
        f := hashes[h]
        if f != nil {
            return f()
        }
    }
    panic("crypto: requested hash function is unavailable")
}
Run Code Online (Sandbox Code Playgroud)

由于每个程序包都充当命名空间,因此每个程序包都可以具有自己的New版本。在bufio.go中可以创建多种类型,因此没有独立的New函数。在这里,您会发现类似NewReader和NewWriter的功能。

  • log 和 bufio 示例似乎是返回指针的函数,而 crypto Hash 似乎是一个构造函数方法,更像您在 Java 等其他 OOP 语言中所期望的那样。Hash New() 方法也不返回指针,它返回一个新的 Hash。从这个意义上说,它看起来更像是一个工厂而不是初始化器。我只是想知道这一点,因为使用具有任何复杂性的 _new_ 函数都会使嵌入类型失去与其构造函数的连接,或者如果您希望维持伪继承,则强制您重新实现它。 (2认同)

K-G*_*Gun 6

另一种方式是;

package person

type Person struct {
    Name string
    Old  int
}

func New(name string, old int) *Person {
    // set only specific field value with field key
    return &Person{
        Name: name,
    }
}
Run Code Online (Sandbox Code Playgroud)

  • @DevX 不,因为这是包的主要(可能是唯一)类型。您可以将其用作“person.New(name, old)”。与“person.NewPerson(name, old)”相比,它有些结巴。 (6认同)
  • person 不是变量,而是包:) 所以 New 是一个 func,而不是一个方法。 (3认同)

gui*_*ere 6

如果您想强制使用工厂函数,请使用小写的第一个字符来命名您的结构(您的类)。那么,就不可能直接实例化结构体,需要工厂方法。

这种基于第一个字符小写/大写的可见性也适用于结构字段和函数/方法。如果您不想允许外部访问,请使用小写。

  • @dynom,我理解你的观点。但是,开发人员实例化我的结构并忘记(或不知道)调用构造函数是否存在巨大的风险?因此,我的每个接收此类结构的方法都必须进行检查以确保实例已初始化。 (3认同)