2017-07-19 189 views
2

在Effective Go中,它是clearly stated,在字段未导出的情况下(以小写字母开头),getter方法应具有相同的字段名称,但应以大写字母开头;他们给出的例子是作为字段的owner和作为方法的Owner。他们明确建议不要在获取方法名称前面使用GetGo Getter方法vs字段,正确命名

我经常遇到需要将字段导出为JSON封送,与ORM一起使用或与其他反射相关的目的(IIRC反射可以读取但不能修改未导出的字段)的情况,所以我的字段需要被调用在上面的例子中为Owner,因此不能有一个Owner方法。

有没有一种习惯的命名方式来解决这种情况?

编辑:这里是我遇到的一个具体的例子:

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 
+1

只需使用“获取”前缀即可。这就是[Go protobuf generator](https://github.com/golang/protobuf)所做的。 –

+0

@TimCooper我听到你的声音,但我几乎宁愿打乱字段名称,因为接口方法名称会出现在更多的地方(并在更多的包中使用),对于直观和习惯用法来说更重要。 –

+4

如果导出字段,为什么要使用Getter/Setter?这只是让界面更容易混淆,通过允许多种方式来完成同样的事情。 – Flimzy

回答

2

如果您的字段被导出,请不要使用getter和setter。这只是混淆了界面。

如果您需要一个getter或setter,因为它执行某些操作(验证,格式化等),或者因为您需要该结构来满足接口,那么不要导出基础字段!您需要一个getter/setter,然后使用两个结构体,一个导出和一个私有,并定义一个自定义的JSON编组器(或者是一个自定义的JSON编组器),或者定义一个自定义的JSON编组器在公共的那个DB访问方法):

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, 
    }) 
} 
+0

我明白了,这是可行的,但我想避免。请参阅我的OP编辑,了解我正在尝试做什么的更多信息。 –

+1

有趣 - 你说得对,使用两个单独的结构可能不会太糟糕。这是一个处理编组而不用自定义代码的变体 - 具有字段的结构嵌入到具有以下方法的外部结构中:https://play.golang.org/p/l_-PRmmydG –

+1

使用接口是使用这两个结构的一个很好的理由。不过,你仍然应该使用这两个结构,因为同时拥有getter/setter和导出的字段仍然是令人困惑和糟糕的设计。 – Flimzy

0

我要张贴此作为一个答案,考虑@ Flimzy的答案,这让很多的意义后得出。

其基本思想是将一个结构与导出字段用于封送处理,另一个单独结构的唯一目的是提供满足所需接口的方法。

这不需要自定义编组代码,并给出了一个明确的含义,以每结构,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()) 
} 

它是否应该user以上User的问题是有点不清楚,我 - 因为它可能对于在Go模板中可见的类型是有意义的,例如{{euser.User.SomeOtherField}}因此,如果需要,允许所有字段都可访问。无论如何,上述答案的功能都是一样的。

+0

该解决方案仍然导出嵌入的'用户名'字段。试试这个:'x:= ExportedUser {user:user {用户名:“joe”}}; fmt.Println(x.Username)'。你可以通过将'* user'命名为'ExportedUser'中的一个字段来解决这个问题,而不是像'type ExportedUser struct {user * user}'中那样嵌入它' – Flimzy

+0

@Flimzy有效点 - 尽管我的例子中的代码编译了,如果用户只有字段,而且ExportedUser只有方法,并且排除Go的语法糖(将u.FieldName转换为u.embeddedStruct.FieldName)的任何可能性都会导致混淆,那么将会更加清楚明确。 –

+1

当然,你的代码是有效的,所以应该编译。它只是不能解决通过getter/setter模式和导出变量暴露相同数据的问题。但我认为你了解这些影响,所以现在你可以做出你的决定:) – Flimzy