254

我有一个结构,我希望用一些合理的默认值对其进行初始化。

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

我注意到了 init 方法,但那是在包级别。还有其他类似的东西可以在结构级别使用吗?

如果不是,那么 Go 中此类事物的公认最佳实践是什么?

4

10 回答 10

258

当零值不能产生合理的默认值或结构初始化需要某些参数时,有一些构造函数的等价物。

假设你有一个这样的结构:

type Thing struct {
    Name  string
    Num   int
}

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

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

当你的结构足够简单时,你可以使用这个精简的结构:

func NewThing(someParameter string) *Thing {
    return &Thing{someParameter, 33}
}

如果您不想返回指针,那么一种做法是调用函数makeThing而不是NewThing

func makeThing(name string) Thing {
    return Thing{name, 33}
}

参考:Effective Go 中的新分配

于 2013-08-08T12:10:16.137 回答
164

实际上有两种公认的最佳实践:

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

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

于 2013-08-08T12:14:23.763 回答
50

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

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

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

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

我想向您展示这个版本的原因是通常可以使用“内联”文字而不是“构造函数”调用。

a := NewThing("foo")
b := &Thing{"foo", 33}

现在*a == *b

于 2013-08-08T12:16:44.580 回答
15

我喜欢这篇文的解释:

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

登录

// 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}
}

bufio.go

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

加密货币.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")
}

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

于 2018-01-25T15:49:41.937 回答
12

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

结果是:

Hello: 5
于 2014-09-30T16:01:03.363 回答
4

另一种方法是;

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,
    }
}
于 2015-11-26T20:14:35.543 回答
3

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

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

于 2019-05-05T13:16:20.857 回答
1

在 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}
}

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

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
}
于 2019-10-31T15:10:51.523 回答
0

Golang 在其官方文档中并不是 OOP 语言。Golang struct 的所有字段都有一个确定的值(不像 c/c++),因此构造函数不像 cpp 那样必要。如果您需要为某些字段分配一些特殊值,请使用工厂函数。Golang 的社区建议使用 New.. 模式名称。

于 2014-02-11T06:51:04.370 回答
-2

我是新来的。我有一个来自其他语言的模式,它有构造函数。并将工作。

  1. 创建一个init方法。
  2. 使该init方法成为(对象)一次例程。它仅在第一次被调用时运行(每个对象)。
func (d *my_struct) Init (){
    //once
    if !d.is_inited {
        d.is_inited = true
        d.value1 = 7
        d.value2 = 6
    }
}
  1. 在此类的每个方法的顶部调用 init。

当您需要后期初始化(构造函数太早)时,此模式也很有用。

优点:它隐藏了类中的所有复杂性,客户端不需要做任何事情。

缺点:你必须记得在类的每个方法的Init顶部调用。

于 2020-04-05T18:47:05.707 回答