简体   繁体   English

Go 接口字段

[英]Go Interface Fields

I'm familiar with the fact that, in Go, interfaces define functionality, rather than data.我熟悉这样一个事实,即在 Go 中,接口定义功能,而不是数据。 You put a set of methods into an interface, but you are unable to specify any fields that would be required on anything that implements that interface.您将一组方法放入一个接口中,但您无法指定实现该接口的任何内容所需的任何字段。

For example:例如:

// Interface
type Giver interface {
    Give() int64
}

// One implementation
type FiveGiver struct {}

func (fg *FiveGiver) Give() int64 {
    return 5
}

// Another implementation
type VarGiver struct {
    number int64
}

func (vg *VarGiver) Give() int64 {
    return vg.number
}

Now we can use the interface and its implementations:现在我们可以使用接口及其实现:

// A function that uses the interface
func GetSomething(aGiver Giver) {
    fmt.Println("The Giver gives: ", aGiver.Give())
}

// Bring it all together
func main() {
    fg := &FiveGiver{}
    vg := &VarGiver{3}
    GetSomething(fg)
    GetSomething(vg)
}

/*
Resulting output:
5
3
*/

Now, what you can't do is something like this:现在,你不能做的是这样的事情:

type Person interface {
    Name string
    Age int64
}

type Bob struct implements Person { // Not Go syntax!
    ...
}

func PrintName(aPerson Person) {
    fmt.Println("Person's name is: ", aPerson.Name)
}

func main() {
    b := &Bob{"Bob", 23}
    PrintName(b)
}

However, after playing around with interfaces and embedded structs, I've discovered a way to do this, after a fashion:然而,在玩弄接口和嵌入式结构之后,我发现了一种方法来做到这一点,以一种时尚的方式:

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
}

Because of the embedded struct, Bob has everything Person has.由于嵌入了结构体,Bob 拥有 Person 所拥有的一切。 It also implements the PersonProvider interface, so we can pass Bob into functions that are designed to use that interface.它还实现了 PersonProvider 接口,因此我们可以将 Bob 传递给旨在使用该接口的函数。

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{
        5,
        Person{"Bob", 23},
    }
    DoBirthday(b)
    SayHi(b)
    fmt.Printf("You're %v years old now!", b.Age)
}

Here is a Go Playground that demonstrates the above code.这是一个演示上述代码的 Go Playground

Using this method, I can make an interface that defines data rather than behavior, and which can be implemented by any struct just by embedding that data.使用这种方法,我可以创建一个定义数据而不是行为的接口,并且任何结构都可以通过嵌入该数据来实现该接口。 You can define functions that explicitly interact with that embedded data and are unaware of the nature of the outer struct.您可以定义与嵌入数据显式交互并且不知道外部结构的性质的函数。 And everything is checked at compile time!并且在编译时检查一切! (The only way you could mess up, that I can see, would be embedding the interface PersonProvider in Bob , rather than a concrete Person . It would compile and fail at runtime.) (我可以看到,唯一可能搞砸的方法是将接口PersonProvider嵌入Bob ,而不是具体的Person 。它会在运行时编译并失败。)

Now, here's my question: is this a neat trick, or should I be doing it differently?现在,这是我的问题:这是一个巧妙的技巧,还是我应该以不同的方式做?

It is definitely a neat trick.这绝对是一个巧妙的技巧。 However, exposing pointers still makes direct access to data available, so it only buys you limited additional flexibility for future changes.但是,暴露指针仍然可以直接访问数据,因此它只会为您带来有限的额外灵活性以应对未来的变化。 Also, Go conventions do not require you to always put an abstraction in front of your data attributes .此外, Go 约定并不要求您始终将抽象放在数据属性前面

Taking those things together, I would tend towards one extreme or the other for a given use case: either a) just make a public attribute (using embedding if applicable) and pass concrete types around or b) if exposing the data seems to complicate some implementation change you think is likely, expose it through methods.将这些事情放在一起,对于给定的用例,我会倾向于一种极端或另一种极端:要么 a) 只创建一个公共属性(如果适用,使用嵌入)并传递具体类型或 b) 如果公开数据似乎使某些复杂您认为可能的实现更改,通过方法公开它。 You're going to be weighing this on a per-attribute basis.您将在每个属性的基础上对此进行权衡。

If you're on the fence, and the interface is only used within your project , maybe lean towards exposing a bare attribute: if it causes you trouble later, refactoring tools can help you find all the references to it to change to a getter/setter.如果您在围栏上,并且该接口仅在您的项目中使用,则可能倾向于暴露一个裸属性:如果以后给您带来麻烦,重构工具可以帮助您找到对其的所有引用以更改为 getter/二传手。


Hiding properties behind getters and setters gives you some extra flexibility to make backwards-compatible changes later.将属性隐藏在 getter 和 setter 之后为您提供了一些额外的灵活性,以便稍后进行向后兼容的更改。 Say you someday want to change Person to store not just a single "name" field but first/middle/last/prefix;假设有一天你想改变Person来存储不只是一个“名称”字段,而是 first/middle/last/prefix; if you have methods Name() string and SetName(string) , you can keep existing users of the Person interface happy while adding new finer-grained methods.如果你有方法Name() stringSetName(string) ,你可以在添加新的更细粒度的方法的同时让Person界面的现有用户满意。 Or you might want to be able to mark a database-backed object as "dirty" when it has unsaved changes;或者,您可能希望能够将数据库支持的对象在未保存更改时标记为“脏”; you can do that when data updates all go through SetFoo() methods.当数据更新全部通过SetFoo()方法时,您可以这样做。 (You could do it other ways, too, like stashing the original data somewhere and comparing when a Save() method is called.) (您也可以通过其他方式进行操作,例如将原始数据存放在某处并比较何时调用Save()方法。)

So: with getters/setters, you can change struct fields while maintaining a compatible API, and add logic around property get/sets since no one can just do p.Name = "bob" without going through your code.所以:使用 getter/setter,您可以在维护兼容 API 的同时更改结构字段,并围绕属性 get/set 添加逻辑,因为没有人可以不通过代码就执行p.Name = "bob"

That flexibility is more relevant when the type is complicated (and the codebase is big).当类型复杂(并且代码库很大)时,这种灵活性更重要。 If you have a PersonCollection , it might be internally backed by an sql.Rows , a []*Person , a []uint of database IDs, or whatever.如果您有PersonCollection ,它可能由sql.Rows[]*Person 、数据库 ID 的[]uint或其他任何内容在内部支持。 Using the right interface, you can save callers from caring which it is, the way io.Reader makes network connections and files look alike.使用正确的界面,您io.Reader调用者关心它是什么,就像io.Reader使网络连接和文件看起来一样。

One specific thing: interface s in Go have the peculiar property that you can implement one without importing the package that defines it;一件特别的事情:Go 中的interface有一个特殊的属性,你可以在不导入定义它的包的情况下实现它; that can help you avoid cyclic imports .这可以帮助您避免循环导入 If your interface returns a *Person , instead of just strings or whatever, all PersonProviders have to import the package where Person is defined.如果您的接口返回*Person ,而不仅仅是字符串或其他任何东西,则所有PersonProviders都必须导入定义Person的包。 That may be fine or even inevitable;这可能很好,甚至是不可避免的; it's just a consequence to know about.这只是一个结果。


But again, the Go community does not have a strong convention against exposing data members in your type's public API .但同样, Go 社区没有强烈的约定来反对在您的类型的公共 API 中公开数据成员 It's left to your judgment whether it's reasonable to use public access to an attribute as part of your API in a given case, rather than discouraging any exposure because it could possibly complicate or prevent an implementation change later.在给定的情况下,将属性的公共访问用作 API 的一部分是否合理,这取决于您的判断,而不是阻止任何公开,因为它可能会使以后的实现更改复杂化或阻止实现更改。

So, for example, the stdlib does things like let you initialize an http.Server with your config and promises that a zero bytes.Buffer is usable.因此,例如,stdlib 会做一些事情,例如让您使用配置初始化http.Server并承诺零bytes.Buffer可用。 It's fine to do your own stuff like that, and, indeed, I don't think you should abstract things away preemptively if the more concrete, data-exposing version seems likely to work.像那样做你自己的事情很好,事实上,如果更具体的数据公开版本似乎可行,我认为你不应该先发制人地抽象出来。 It's just about being aware of the tradeoffs.这只是了解权衡。

If I correctly understand you want to populate one struct fields into another one.如果我正确理解您想将一个结构字段填充到另一个字段中。 My opinion not to use interfaces to extend.我的观点是不要使用接口来扩展。 You can easily do it by the next approach.您可以通过下一个方法轻松完成。

package main

import (
    "fmt"
)

type Person struct {
    Name        string
    Age         int
    Citizenship string
}

type Bob struct {
    SSN string
    Person
}

func main() {
    bob := &Bob{}

    bob.Name = "Bob"
    bob.Age = 15
    bob.Citizenship = "US"

    bob.SSN = "BobSecret"

    fmt.Printf("%+v", bob)
}

https://play.golang.org/p/aBJ5fq3uXtt https://play.golang.org/p/aBJ5fq3uXtt

Note Person in Bob declaration.注意Bob声明中的Person This will be made the included struct field be available in Bob structure directly with some syntactic sugar.这将使包含的结构字段直接在Bob结构中可用一些语法糖。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM