英文:
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()
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论