【问题标题】:Understanding embedding in Go理解 Go 中的嵌入
【发布时间】:2014-04-14 01:04:56
【问题描述】:

我正在尝试理解 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 嵌入(不是接口)来实现最后的输出?

【问题讨论】:

  • 嵌入不是子类化的替代方法。在 Go 编程时忘记子类化:你的设计会更好。
  • @Volker:我同意。但是我想了解如何重用上游项目的代码。我需要更改一个(许多)方法,并且上游没有声明/使用的接口(与这段代码相关)。

标签: go embedding subclassing


【解决方案1】:

没有。嵌入是一种方式。嵌入有点太独特了,不能仅仅称之为“语法糖”——它可以用来满足接口。 Android应该满足界面

type TalkViaer interface {
    TalkVia()
}

因为它嵌入了一个人。但是,从本质上讲,您必须记住嵌入只是一种非常聪明的方法,可以访问结构的成员。没有更多或更少。当p 被传递给TalkVia 时,它会得到一个Person,并且由于该人不知道它的所有者,所以它无法引用它的所有者。

您可以通过在Person 中保存一些所有者变量来解决此问题,但嵌入不是继承。根本没有“超级”或“扩展器”或类似的概念。这只是给一个结构一个特定的方法集的一种非常方便的方法。


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

type Android struct {
    P person
}

我们都同意,如果我先调用a := Android{},然后再调用a.P.TalkVia(),它就不会调用Android 的任何方法,对吧?即使那是 Java 或 C++,也没有任何意义,因为它是成员。

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

【讨论】:

  • 最后一次编辑很有帮助。但是type Android struct {Person}type Android struct {*Person}之间有区别吗?
  • 唯一的区别是嵌入类型是指针。在嵌入方面,行为完全相同。
【解决方案2】:

我认为interface 会更接近您想要实现的目标,而不是嵌入,我知道这不是您提出的问题。通过提供接口,两个 Person 和 Android 结构可以实现满足接口的 reiver 方法。 TalkVia 将是添加到界面的下一个方法,然后会给出所需的输出。

 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()
 }

基于您的代码的完整示例,接口位于Go Playground Example

根据您关于无法修改 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

完整示例在此second play ground example 中,但简而言之,执行以下操作:

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()
}

【讨论】:

  • 不能修改上游代码但又想复用部分功能怎么办。在这种情况下,您使用定义 Person 的包(带有 Talk 和 TalkVia);并且您想定义 Android 重用 TalkVia 但重新实现 Talk。
  • 我明白你在说什么。我已经修改了第二个示例,它能够产生您正在寻找的输出。
猜你喜欢
  • 2016-05-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-08-27
  • 1970-01-01
相关资源
最近更新 更多