英文:
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'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's a concrete example of what I'm running into:
type User struct {
Username string `db:"username" json:"username"`
// ...
}
// code in this package needs to do json.Unmarshal(b, &user), etc.
.
// BUT, I want code in other packages to isolate themselves from
// the specifics of the User struct - they don't know how it'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'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:"owner"`
}
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())
}
上面的问题是关于上面的user
或User
应该是哪个,对我来说有点不清楚-因为在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:"username"`
}
type ExportedUser struct {
// EDIT: explicitly doing "user *user" instead of just doing "*user"
// helps avoid confusion between between field names and methods
user *user
}
func (u ExportedUser) Username() string { return u.user.Username }
func main() {
fmt.Printf("Test: %q", ExportedUser{user: &user{Username: "joe"}}.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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论