理解Go语言中的嵌入

huangapple go评论90阅读模式
英文:

Understanding embedding in Go

问题

我正在尝试理解Google Go的嵌入机制(作为子类化的替代方法)。下面是一个简单的程序,总结了我对这种方法的问题:

package main

import "fmt"

type Person struct {
    Name string
}

func (p *Person) Talk() {
    fmt.Println("Hi, my name is Person")
}

func (p *Person) TalkVia() {
    fmt.Println("TalkVia ->")
    p.Talk()
}

type Android struct {
    Person
}

func (p *Android) Talk() {
    fmt.Println("Hi, my name is Android")
}

func main() {
    fmt.Println("Person")
    p := new(Person)
    p.Talk()
    p.TalkVia()

    fmt.Println("Android")
    a := new(Android)
    a.Talk()
    a.TalkVia()
}

输出结果为:

Person
Hi, my name is Person
TalkVia ->
Hi, my name is Person
Android
Hi, my name is Android
TalkVia ->
Hi, my name is Person

但是,如果我在另一种语言中使用子类化,输出将会是:

Person
Hi, my name is Person
TalkVia ->
Hi, my name is Person
Android
Hi, my name is Android
TalkVia ->
Hi, my name is Android

有没有办法使用Google Go的嵌入机制来实现最后一种输出(而不是使用接口)?

英文:

I am trying understanding Google Go's embedding mechanism (as an alternative to subclassing). Below is a simple program that summarizes my problem with the approach:

package main

import "fmt"

type Person struct {
    Name string
}

func (p *Person) Talk() {
    fmt.Println("Hi, my name is Person")
}

func (p *Person) TalkVia() {
    fmt.Println("TalkVia ->")
    p.Talk()
}

type Android struct {
    Person
}

func (p *Android) Talk() {
    fmt.Println("Hi, my name is Android")
}

func main() {
    fmt.Println("Person")
    p := new(Person)
    p.Talk()
    p.TalkVia()

    fmt.Println("Android")
    a := new(Android)
    a.Talk()
    a.TalkVia()
}

The output is:

Person
Hi, my name is Person
TalkVia ->
Hi, my name is Person
Android
Hi, my name is Android
TalkVia ->
Hi, my name is Person

but if I was subclassing (in another language), the output will be:

Person
Hi, my name is Person
TalkVia ->
Hi, my name is Person
Android
Hi, my name is Android
TalkVia ->
Hi, my name is Android

Is there a way to achieve this last output with google go embedding (not interfaces)?

答案1

得分: 6

嵌入是单向的。嵌入稍微有点独特,不能仅仅称之为“语法糖”——它可以用来满足接口。Android应该满足接口

type TalkViaer interface {
    TalkVia()
}

因为它嵌入了一个人。然而,你必须记住,嵌入只是一种非常巧妙的方式,可以访问结构体的成员。没有更多也没有更少。当p传递给TalkVia时,它得到一个Person,由于这个人没有意识到它的所有者,它将无法引用它的所有者。

你可以通过在Person中持有一些所有者变量来解决这个问题,但是嵌入不是继承。没有“super”或“extender”或其他类似的概念。它只是一种非常方便的方式,给一个结构体提供一组特定的方法。

编辑:也许需要更多的解释。但只是一点点。

type Android struct {
    P person
}

我们都同意,如果我执行a := Android{}然后a.P.TalkVia(),它不会调用任何Android的方法,对吗?即使是在Java或C++中,这也没有意义,因为它是一个成员。

嵌入仍然只是一个成员。它只是Android拥有的一段数据,没有更多也没有更少。在语法层面上,它将所有方法都赋予了Android,但它仍然只是一个成员,你不能改变这一点。

英文:

No. Embedding is a one way thing. Embedding is slightly too unique to merely call it "syntactic sugar" -- it can be used to satisfy interfaces. Android should satisfy the interface

type TalkViaer interface {
    TalkVia()
}

because it embeds a person. However, at its heart you have to remember that embedding is just a really clever way of giving access to a struct's member. Nothing more or less. When p is passed into TalkVia it gets a Person, and since that person has no conception of its owner, it won't be able to reference its owner.

You can work around this by holding some owner variable in Person, but embedding is not inheritance. There's simply no conception of a "super" or an "extender" or anything like that. It's just a very convenient way to give a struct a certain method set.


Edit: Perhaps a little more explanation is in order. But just a little.

type Android struct {
    P person
}

We both agree that if I did a := Android{} and then a.P.TalkVia() it wouldn't call any of Android's methods, right? Even if that was Java or C++, that wouldn't make sense, since it's a member.

Embedding is still just a member. It's just a piece of data owned by the Android, no more, no less. At a syntactic level, it confers all of its methods to Android, but it's still just a member and you can't change that.

答案2

得分: 1

我认为使用interface比使用嵌入更接近你想要实现的目标,尽管我知道这不是你提出的问题。通过提供一个接口,Person和Android结构体可以实现满足接口的接收者方法。接下来,可以将TalkVia方法添加到接口中,从而得到所需的输出。

以下是基于你的代码的完整示例,其中包含接口的Go Playground示例:

type Talker interface {
    Talk()
}

func (p *Android) Talk() {
    fmt.Println("Hi, my name is", p.Name)
}

func main() {
    p := Person{"Person"}
    p.Talk()
    p.TalkVia()
}

根据你关于无法修改Person实现的评论,我修改了示例,添加了一个构造函数和一个嵌入的结构体,得到以下结果:

Person
Hi, my name is Person
TalkVia ->
Hi, my name is Person
Android
Hi, my name is Android
Hi, my name is Android
TalkVia ->
Hi, my name is Android

完整示例在这个第二个Playground示例中,但简要来说,它执行以下操作:

type Android struct {
    Person
    Name string
}

func NewAndroid(name string) Android {
    return Android{Person{name}, name}
}

现在我们可以创建一个Android对象,并将其用作Android和Person。实际上,因为它现在嵌入了实现Talker接口的Person,所以它也可以直接调用TalkVia方法。

func main() {
    ...

    a := NewAndroid("Android")
    a.Talk()
    a.TalkVia()
    ap := a.Person
    ap.Talk()
    ap.TalkVia()
}
英文:

I think that an interface would be closer to what you want to acheive rather than Embedding, which I know is not what your question is posed as. By providing an interface the two Person and Android stucturs can have reiver methods implemented that meet the interface. The TalkVia would be the next method to add to the interface which would then give the desired output.

 type Talker interface {
       Talk()
 }
 ...
 func (p *Android) Talk() {
       fmt.Println("Hi, my name is ", p.Name )
 }
 ...
 func main() {
       p := Person { "Person" }
       p.Talk()
       p.TalkVia()
 }

Full example based on your code with interface here at the Go Playground Example

Based on your comment about not being able to modify the implementation of Person. I have modified example with a constructor and an embedded struct to produce the following:

Person
Hi, my name is Person
TalkVia ->
Hi, my name is Person
Android
Hi, my name is Android
Hi, my name is Android
TalkVia ->
Hi, my name is Android

The full example is in this second play ground example but in short form does the following:

type Android struct {
    Person
    Name string
}
...
func NewAndroid(name string) Android {
    return Android { Person { name }, name }
}

Now we can create an Android and use it as a Android and a Person. In fact because it is now embedding Person which implements the Talker interface it can also just directly call the TalkVia method

func main() {
     ...

     a := NewAndroid("Android")
     a.Talk()
     a.TalkVia()
     ap := a.Person
     ap.Talk()
     ap.TalkVia()
}

huangapple
  • 本文由 发表于 2014年4月14日 09:04:56
  • 转载请务必保留本文链接:https://go.coder-hub.com/23050408.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定