Reputation: 6063
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)?
Upvotes: 1
Views: 299
Reputation: 7385
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()
}
Upvotes: 1
Reputation: 22236
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.
Upvotes: 6