回答

收藏

Go 接口字段

技术问答 技术问答 260 人阅读 | 0 人回复 | 2023-09-12

我熟悉 Go 中,界面定义功能,而不是数据。您将一组方法放入界面中,但您无法指定界面所需的任何字段。
- f, p' A$ w3 s/ @& {例如:
+ o9 N& S8 L. q/ L
    // Interfacetype Giver interface    Give() int64}// One implementationtype FiveGiver struct {}func (fg *FiveGiver) Give() int64    return 5}// Another implementationtype VarGiver struct    number int64}func (vg *VarGiver) Give() int64    return vg.number}
    + Y6 c) ?. i( u$ y+ I
现在我们可以使用接口及其实现:0 l' j+ B9 c. t
    // A function that uses the interfacefunc GetSomething(aGiver Giver)    fmt.Println("The Giver gives: ",aGiver.Give()}// Bring it all togetherfunc main()    fg := &FiveGiver{}    vg := &VarGiver{3}    GetSomething(fg)    GetSomething(vg)}/*Resulting output:53*/
    : t+ _) ?6 R- f: J: k3 s
现在,你不能做这样的事:
* b- N6 Y0 [0 l# }6 g5 M/ y
    type Person interface    Name string    Age int64}type Bob struct implements Person { // Not Go syntax!    ...}func PrintName(aPerson Person)    fmt.Println(&quoterson's name is: ",aPerson.Name)}func main()    b := &Bob{"Bob",23}    PrintName(b)}7 e. `% J1 T7 L% S2 J1 p$ x. j
然而,在玩了界面和嵌入式结构后,我找到了一种时尚的方法来做到这一点:6 Y  h: ^$ ^% Y
    type PersonProvider interface    GetPerson() *Person}type Person struct    Name string    Age  int64}func (p *Person) GetPerson() *Person    return p}type Bob struct    FavoriteNumber int64    Person}6 Y' z5 u+ E8 U$ G4 \
结构体嵌入,Bob 拥有 Person 的一切。它还实现了 PersonProvider 接口,所以我们可以Bob 传递给使用该接口的函数。
0 J/ ~7 {$ X" c$ a6 Z! J9 S
    func DoBirthday(pp PersonProvider)    pers := pp.GetPerson()    pers.Age  = 1}func SayHi(pp PersonProvider)    fmt.Printf("Hello,%v!\r",pp.GetPerson().Name)}func main()    b := &Bob{        5Person{"Bob",          DoBirthday(b)    SayHi(b)    fmt.Printf("You're %v years old now!",b.Age)}5 E1 O. O' Q0 q  r' e  F
使用这种方法,我可以创建一个定义数据而不是行为的接口,任何结构都可以通过嵌入数据来实现。您可以定义与嵌入数据显式交互的函数,并且不知道外部结构的性质。并在编译过程中检查一切!(我可以看到,唯一可能搞砸的方法是嵌入接口PersonProvider中Bob,而不是具体的 中Person。操作时会编译失败。
! b% [5 a* u: l% V1 i1 i1 `现在,这是我的问题:这是一项巧妙的技能,还是我应该以不同的方式去做?% o. c3 G# m1 i4 @9 t2 d  `% E" L! e
                                                               
& s( b$ X3 W0 a2 E    解决方案:                                                               
" |3 V0 W7 ^  o# e, q2 b                                                                这绝对是一项聪明的技能。然而,公共指针仍然可以直接访问数据,因此它只会给您带来有限的额外灵活性来应对未来的变化。Go 协议不要求你总是把抽象放在数据属性面前0 ~, E. J. d+ S$ Y7 I
把这些东西放在一起,对于给定的用例,我倾向于一个极端或另一个极端:a) 只创建公共属性(如果适用,使用嵌入)并传递具体类型或 b) 如果公开数据似乎改变了你认为可能的一些复杂性,则通过方法公开它。您将在每个属性的基础上权衡它。
+ S: a9 j. I3 U- u9 W如果你在围栏上,接口只是使用您的项目,它可能倾向于暴露裸体属性:如果将来给你带来麻烦,重建工具可以帮助你找到所有的参考,并将其更改为 getter/二传手。/ k7 l0 b/ a' S( R
属性隐藏在 getter 和 setter 之后,它为您提供了一些额外的灵活性,以便以后进行兼容化。假设有一天你想改变Person存储的不仅仅是名称字段first/middle/last/prefix;如果您有方法Name() string和SetName(string),则可以Person添加新的细粒度方法来满足界面的现有用户。或者,您可能希望在未保存和更改的情况下将数据库支持的对象标记为脏;当数据更新通过时SetFoo()你可以这样做。(也可以通过其他方式操作,比如将原始数据存储在某个地方并存储在某个地方。Save()调用方法时进行比较。)
2 O7 X, j" l" r所以:使用 getter/setter,维护时可以兼容 API 同时改变结构字段,围绕属性 get/set 添加逻辑,因为没有人能添加逻辑p.Name = "bob"不用你的代码就能做到。
5 P2 V; v! E) e! d/ @; B/ m当类型复杂(代码库大)时,这种灵活性更为重要。如果你有PersonCollection,它可能由sql.Rows、[]*Person、[]uint数据库 ID 或内部支持任何其他内容。使用正确的界面可以避免呼叫者关心它是什么,这样io.Reader使网络连接看起来与文件相似。4 x/ a- ?. E& M) r
一件特别的事:interfaceGo 中的 s 有一个特殊的属性,可以在不导入定义其包的情况下实现;这可以帮助你避免循环导入。如果您的接口返回 a *Person,而不仅仅是字符串或任何其他东西PersonProviders必须在Person定义位置导入包。这可能是好的,甚至是不可避免的;这只是结果。
' S  f; S# U' H( k9 [# M但同样,Go 社区没有强烈的协议反对你类型的公共 API 公开数据成员。在给定的情况下,将属性的公共访问用作 API 的一部分是否合理取决于你,而不是阻止它任何公开,因为它可能会使未来的变化复杂化或防止变化。
3 V) j% i+ _# R9 c例如,stdlib 执行就像让你http.Server使用配置初始化 an并承诺使用零和其他东西bytes.Buffer。这样做你自己的事情很好,事实上,如果更具体的数据披露版本似乎是可行的,我认为你不应该先抽象。这只是理解权衡。
分享到:
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则