Go Getter方法与字段,适当的命名

huangapple go评论165阅读模式
英文:

Go Getter Methods vs Fields, Proper Naming

问题

在《Effective Go》中,明确指出在字段未导出的情况下(以小写字母开头),getter方法应该与字段名相同,但首字母大写;他们给出的示例是字段名为owner,方法名为Owner。并且他们明确建议不要在getter方法名前使用Get

我经常遇到这样的情况:我需要将字段导出以进行JSON编组、与ORM一起使用或进行其他反射相关的操作(如果我没记错,反射可以读取但不能修改未导出的字段),所以在上面的示例中,我的字段需要被称为Owner,因此不能有一个Owner方法。

有没有一种习惯用法来解决这种情况的命名问题?

编辑:这里有一个具体的例子,说明了我遇到的问题:

type User struct {
    Username string `db:"username" json:"username"`
    // ...
}

// 本包中的代码需要执行json.Unmarshal(b, &user)等操作。

// 但是,我希望其他包中的代码能够与User结构的具体细节隔离开来 - 它们不知道它是如何实现的,只知道它有一个Username字段。即
package somethingelse

type User interface {
    Username() string
}

// 这个其他包中的代码的其余部分针对接口工作,并且不直接了解结构体 - 这是有意设计的,因为它很重要,可以在不影响此代码的情况下进行更改。

<details>
<summary>英文:</summary>

In Effective Go, it&#39;s [clearly stated][1] that in the case where a field is unexported (begins with a lower case letter) a getter method should have the same field name but starting with upper case; the example they give is a `owner` as a field and `Owner` as a method. And they explicitly advise against using `Get` in front of the getter method name.

I frequently encounter situations where I need the field to be exported for JSON marshaling, use with an ORM, or other reflection related purposes (IIRC reflect can read but not modify unexported fields), so my field would need to be called `Owner` in the example above and thus cannot have an `Owner` method.

Is there an idiomatic way of naming which addresses this situation?

  [1]: https://golang.org/doc/effective_go.html#Getters

**EDIT**: Here&#39;s a concrete example of what I&#39;m running into:


    type User struct {
        Username string `db:&quot;username&quot; json:&quot;username&quot;`
        // ...
    }

    // code in this package needs to do json.Unmarshal(b, &amp;user), etc.

.

    // BUT, I want code in other packages to isolate themselves from
    // the specifics of the User struct - they don&#39;t know how it&#39;s
    // implemented, only that it has a Username field.  i.e.
    package somethingelse
    
    type User interface {
        Username() string
    }
    
    // the rest of the code in this other package works against the
    // interface and is not aware of the struct directly - by design,
    // because it&#39;s important that it can be changed out without
    // affecting this code


</details>


# 答案1
**得分**: 15

如果你的字段是公开的就不要使用getter和setter这只会混淆接口

如果你需要一个getter或setter来执行某些操作验证格式化等),或者因为你需要结构体满足某个接口那就不要公开底层字段

如果你需要一个公开的字段用于JSON数据库访问等并且你需要一个getter/setter那么可以使用两个结构体一个是公开的一个是私有的并在公开的结构体上定义一个自定义的JSON编组器或数据库访问方法):

```go
type jsonFoo struct {
    Owner string `json:"owner"`
}

type Foo struct {
    owner string
}

func (f *Foo) SetOwner(username string) {
    // 验证/格式化用户名
    f.owner = username
}

func (f *Foo) Owner() string {
    return f.owner
}

func (f *Foo) MarshalJSON() ([]byte, error) {
    return json.Marshal(jsonFoo{
        Owner: f.owner,
    })
}
英文:

Don't use getters and setters if your fields are exported. This just confuses the interface.

If you need a getter or setter because it does something (validation, formatting, etc), or because you need the struct to satisfy an interface, then don't export the underlying field!

If you need an exported field for the sake of JSON, database access, etc, and you need a getter/setter, then use two structs, one exported and one private, and define a custom JSON marshaler (or db access method) on the public one:

type jsonFoo struct {
Owner string `json:&quot;owner&quot;`
}
type Foo struct {
owner string
}
func (f *Foo) SetOwner(username string) {
// validate/format username
f.owner = username
}
func (f *Foo) Owner() string {
return f.owner
}
func (f *Foo) MarshalJSON() ([]byte, error) {
return json.Marshal(jsonFoo{
Owner: f.owner,
})
}

答案2

得分: 3

我将把这个作为答案发布,这是在考虑了@Flimzy的答案后得出的,这个答案非常有道理。

基本思路是有一个具有导出字段的结构体,可以用于编组,另一个单独的结构体的唯一目的是提供满足所需接口的方法。

这不需要自定义编组代码,并且对每个结构体都有明确的含义,我个人认为:

type user struct {
Username string `json:"username"`
}
type ExportedUser struct {
// 编辑:明确使用"user *user"而不仅仅是"*user"有助于避免字段名和方法之间的混淆
user *user
}
func (u ExportedUser) Username() string { return u.user.Username }
func main() {
fmt.Printf("Test: %q", ExportedUser{user: &user{Username: "joe"}}.Username())
}

上面的问题是关于上面的userUser应该是哪个,对我来说有点不清楚-因为在Go模板中可见类型可能是有意义的,例如{{euser.User.SomeOtherField}},从而允许在需要时访问所有字段。无论如何,上面的答案都可以正常工作。

英文:

I'm going to post this as an answer, derived after considering @Flimzy's answer, which makes a lot of sense.

The basic idea is to have one struct with exported fields which can be used for marshaling, and another separate struct which has the sole purpose of providing the methods which meet the needed interface.

This requires no custom marshaling code, and gives a clear meaning to each struct, IMO:

type user struct {
Username string `json:&quot;username&quot;`
}
type ExportedUser struct {
// EDIT: explicitly doing &quot;user *user&quot; instead of just doing &quot;*user&quot;
// helps avoid confusion between between field names and methods
user *user
}
func (u ExportedUser) Username() string { return u.user.Username }
func main() {
fmt.Printf(&quot;Test: %q&quot;, ExportedUser{user: &amp;user{Username: &quot;joe&quot;}}.Username())
}

The question of whether it should be user or User above is a bit unclear to me - since it may make sense for the type to be visible for use in a Go template, e.g. {{euser.User.SomeOtherField}} Thus allowing all of the fields to be accessible if needed. Regardless, the answer above functions the same either way.

huangapple
  • 本文由 发表于 2017年7月19日 19:24:56
  • 转载请务必保留本文链接:https://go.coder-hub.com/45189126.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定