去界面字段 [英] Go Interface Fields

查看:150
本文介绍了去界面字段的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我熟悉的一个事实是,在Go中,接口定义了功能,而不是数据。您将一组方法放到一个接口中,但是您无法指定任何实现该接口的任何字段。



例如:

  //接口
类型Giver接口{
Give()int64
}

//一个实现
类型FiveGiver结构{}

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

//另一个实现
类型VarGiver结构{
数字int64
}

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

现在我们可以使用接口及其实现: p>

  //使用接口
的函数func GetSomething(aGiver Giver){
fmt.Println(给予者给出:,aGiver.Give())
}

//将它们放在一起
func main(){
fg:=& FiveGiver {}
vg:=& VarGiver {3}
GetSomething(fg)
G etSomething(vg)
}

/ *
结果输出:
5
3
* /


$ b

现在,你不能 做的是这样的:

 类型Person接口{
名称字符串
年龄int64
}

类型Bob struct implements Person { // Not Go语法!
...
}

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

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

然而,在使用接口和嵌入式结构之后,我发现了一种方法来实现这一点,以一种方式:

 类型PersonProvider接口{
GetPerson()* Person
}

类型Person结构{
名称字符串
年龄int64
}

func(p * Person)GetPerson()* Person {
return p
}

类型Bob结构{
FavoriteNumber int64
人物
}

由于嵌入式结构,Bob拥有Person的所有内容。它还实现了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 'b%a year)
}

这是一个Go Playground ,演示上面的代码。



使用这个方法,我可以创建一个定义数据而不是行为的接口,并且可以通过嵌入数据来实现任何结构。您可以定义明确与该嵌入数据交互的函数,但不知道外部结构的性质。并且在编译时检查所有内容! (我可以看到,唯一的办法就是将接口 PersonProvider 嵌入到 Bob 中,而不是而不是一个具体的 Person 。它会在运行时编译并失败。)

现在,这是我的问题:这是一个整洁的技巧,还是应该以不同的方式去做?

解决方案

作为您的API的一部分,您可以访问这些字段。我会考虑的另一种方法是保留可嵌入的struct / 接口设置,但是根据getter和setter来定义接口。



隐藏getter和setter后面的属性为您提供一些额外的灵活性,以便稍后进行向后兼容的更改。假设你有一天想改变 Person 来存储不止一个name字段,而是存储first / middle / last / prefix;如果你有方法 Name()string SetName(string),你可以保留 Person 界面高兴,同时添加新的更细粒度的方法。或者您可能希望能够在未保存的更改时将数据库支持的对象标记为脏;您可以在数据更新全部通过 SetFoo()方法时执行此操作。

因此:使用getters / setters,你可以在维护一个兼容的API的同时改变struct字段,并且在属性get / sets周围添加逻辑,因为没有人可以做 p.Name =bob,而不需要通过您的代码。


当你的类型做更复杂的事情时,这种灵活性更加相关。如果您有一个 PersonCollection ,它可能由内部支持 sql.Rows ,一个 [] * Person ,一个 [] uint 数据库ID,或其他。使用正确的界面,您可以保存来电者的身份, io.Reader 使网络连接和文件看起来很相似。



一个特定的事情:Go中的 interface s具有可以在不导入定义它的包的情况下实现的特殊属性;可以帮助您避免循环导入。如果你的接口返回一个 * Person ,那么所有的 PersonProviders 都必须导入<定义了code> Person 。这可能很好,甚至是不可避免的;这只是一个了解的结果。



所有这一切,都没有Go惯例,你必须隐藏所有的数据。 (与C ++相比,这是一个值得欢迎的区别)stdlib可以让你用你的配置初始化一个 http.Server ,并承诺一个零 bytes.Buffer 是可用的。做自己喜欢的事情是很好的,事实上,如果更具体的数据暴露版本起作用,我认为你不必过早抽象。这只是意识到权衡。


I'm familiar with the fact that, in Go, interfaces define functionality, rather than data. 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. It also implements the PersonProvider interface, so we can pass Bob into functions that are designed to use that interface.

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.

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.)

Now, here's my question: is this a neat trick, or should I be doing it differently?

解决方案

It is definitely a neat trick, and works as long as you're cool giving access to those fields as part of your API. The alternative I'd consider on is keeping the embeddable struct/interface setup, but defining the interface in terms of getters and setters.

Hiding properties behind getters and setters gives you some extra flexibility to make backwards-compatible changes later. Say you someday want to change Person to store not just a single "name" field but 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. 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.

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.

That flexibility is more relevant when your type does something more complicated. If you have a PersonCollection, it might be internally backed by an sql.Rows, a []*Person, a []uint of database IDs, or whatever. Using the right interface, you can save callers from caring which it is, the way io.Reader makes network connections and files look alike.

One specific thing: interfaces in Go have the peculiar property that you can implement one without importing the package that defines it; 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. That may be fine or even inevitable; it's just a consequence to know about.

All that said, there's no Go convention that you have to hide all your data. (This is a welcome difference from, say, C++.) The stdlib does things like let you initialize an http.Server with your config and promises that a zero bytes.Buffer is usable. It's fine to do your own stuff like that, and, indeed, I don't think you have to do premature abstraction if the more concrete, data-exposing version works. It's just about being aware of the tradeoffs.

这篇关于去界面字段的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆