5

我从 Java 来到 Go,有些事情让我感到困惑。

例如,让我们考虑以下代码:

package main

import (
    "fmt"
)

type I interface {
    Do()
    MegaDo()
}

type A struct {
}

func (a *A) Do() {
    fmt.Println("A")
}

func (a *A) MegaDo() {
    a.Do()
}

type B struct {
    A
}

func (a *B) Do() {
    fmt.Println("B")
}

var i I

func main() {
    fmt.Println("Hello, playground")

    var i I = &B{}

    i.MegaDo()
}

这里我们有一个I带有方法的接口Do()MegaDo(). Struct在内部A实现了方法和MegaDo调用Do。并且B仅由覆盖A和覆盖Do()

如果我在 Java 中模仿相同的代码,我希望它会打印“B”。但是在 Go 中它会打印“A”。

虽然我有点理解它为什么会发生(因为它是嵌入而不是继承),但我想知道如何在 Go 中模仿同样的事情。例如,我有两个相同接口的实现,它们只有一点不同。在这种情况下如何最大化代码重用?我不敢相信,为了在一个实现中自定义一点逻辑,我必须复制粘贴所有内容并修复我的代码中的一小部分。也许在 Go 中有一些惯用的方法可以做到这一点?

4

2 回答 2

12

Go 没有“类”的子类化或扩展。嵌入类型的方法使用它们的原始类型接收器。在这种情况下,该方法MegaDo在 内提升B,但是当被调用时,它是在A字段上调用的。 B.MegaDo()只是 . 的语法糖B.A.MegaDo()。因此,当它调用Do()它的接收器时,它调用的是A版本,而不是B版本。

处理这个更简单的方法是嵌入一个接口。例如:

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

type Mega struct {
    I
}

func (m Mega) MegaDo() {
    m.Do()
} 

func main() {
    var a A
    var b B
    m := Mega{I: A}
    m.MegaDo()
    m.I = B
    m.MegaDo()
}

注意:在这种情况下,实际上不需要嵌入接口,如果它是一个命名字段,则MegaDo()可以简单地调用。m.i.Do()但是,嵌入它允许其他代码直接调用Do()m而无需知道该字段中实际嵌入了什么类型。另请注意,嵌入接口的实际结果是根据定义嵌入接口的结构也满足相同的接口。

此模式的实际示例:嵌入 sql.DB 和 sql.Tx 类型(QueryRow、Query、Exec 等)的联合方法的 DB 句柄类型。该句柄的用户可以调用这些方法,而不必知道它们是否在事务的上下文中被调用。

于 2018-03-05T15:44:39.073 回答
9

您的问题太抽象,无法很好地回答,但我希望这会有所帮助。

从您要解决的实际问题(业务需求等)开始重新考虑设计,不要尝试使用 Java 设计在 Go 中解决它。Go 没有继承,接口是它唯一的多态形式;您不能以任何合理的方式在 Go 中“模仿动态调度”。

具体来说,关于这个:

我有两个相同接口的实现,它们只有一点点不同。在这种情况下如何最大化代码重用?我不敢相信,为了在一个实现中自定义一点逻辑,我必须复制粘贴所有内容并修复我的代码中的一小部分。

根据代码重用而不是类层次结构重新考虑您的设计,因为没有。如果您有相同接口的两个实现,那很好!Go 有接口,它们工作得很好。如果您在两个实现中重复了一堆代码,要么a)将共享代码抽象为两个实现都可以调用的函数,要么b)如果差异真的那么小,也许它应该是一个带有一些简单切换逻辑的单一实现.

于 2018-03-05T15:43:24.733 回答