转到界面字段

Mat*_* Mc 90 struct interface go

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

例如:

// 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
}
Run Code Online (Sandbox Code Playgroud)

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

// 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
*/
Run Code Online (Sandbox Code Playgroud)

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

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)
}
Run Code Online (Sandbox Code Playgroud)

然而,在使用接口和嵌入式结构之后,我发现了一种方法,在时尚之后:

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
}
Run Code Online (Sandbox Code Playgroud)

由于嵌入式结构,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're %v years old now!", b.Age)
}
Run Code Online (Sandbox Code Playgroud)

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

使用这种方法,我可以创建一个定义数据而不是行为的接口,只需嵌入该数据就可以通过任何结构实现.您可以定义与嵌入数据明确交互的函数,并且不知道外部结构的本质.并且在编译时检查所有内容!(你可以搞砸了,我能看到的唯一方法,将嵌入界面PersonProviderBob,而不是一个具体的Person,它会编译并在运行时失败.)

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

two*_*two 49

这绝对是一个巧妙的技巧,只要你很酷,可以访问这些字段作为API的一部分.我要考虑的替代方案是保留可嵌入的结构/ interface设置,但是根据getter和setter来定义接口.

隐藏getter和setter背后的属性为您提供了一些额外的灵活性,可以在以后进行向后兼容的更改.假设你有一天想改变Person存储而不仅仅是一个"名称"字段,而是第一个/中间/最后一个/前缀; 如果你有方法Name() stringSetName(string),您可以保留现有的用户Person界面快乐,同时增加新的更细粒度的方法.或者,您可能希望能够在数据库支持的对象有未保存的更改时将其标记为"脏"; 当数据更新全部通过SetFoo()方法时,你可以这样做.

所以:使用getter/setter,你可以在保持兼容API的同时更改struct字段,并在属性get/sets周围添加逻辑,因为没有人可以p.Name = "bob"不经过你的代码.

当您的类型执行更复杂的操作时,这种灵活性更具相关性.如果你有一个PersonCollection,它可能是由内部的支持sql.Rows,一个[]*Person,一个[]uint数据库ID,或什么的.使用正确的界面,您可以保护呼叫者免受照顾,io.Reader使网络连接和文件看起来相似.

一个特定的事情:interfaceGo中的s具有特殊属性,您可以在不导入定义它的包的情况下实现它; 这可以帮助您避免循环导入.如果你的接口返回一个*Person,而不是只是字符串或其他什么,都PersonProviders必须导入Person定义的包.这可能是好的,甚至是不可避免的; 这只是了解的结果.

总而言之,没有Go惯例,你必须隐藏所有数据.(这是与C++相比的一个受欢迎的区别.)stdlib做的事情就像让你http.Server用你的配置初始化一样,并承诺零bytes.Buffer可用.做这样的事情很好,事实上,如果更具体的数据暴露版本有效,我认为你不必做过早的抽象.这只是意识到权衡.


Igo*_*ine 8

如果我正确理解您想将一个结构字段填充到另一个字段中。我的观点是不要使用接口来扩展。您可以通过下一个方法轻松完成。

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)
}
Run Code Online (Sandbox Code Playgroud)

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

注意PersonBob声明。这将使包含的结构字段Bob直接在结构中可用一些语法糖。

  • 这是“struct embedding”机制的语法糖,如果你想在分配点上进行赋值,你需要使用更明确的声明: `bob := &Bob{Person: Person{Name: "Bob"}}` (4认同)