在Go中模仿适当的动态调度的惯用方法

时间:2018-03-05 15:37:50

标签: oop go dynamic-dispatch

我要从Java开始,有些事情让我困惑。

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

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调用DoBA组成,仅覆盖Do()

如果我在Java中模仿相同的代码,我希望它可以打印" B"。但在Go中,它打印了#34; A"。

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

2 个答案:

答案 0 :(得分:6)

你的问题太抽象了,无法回答,但我希望这会有所帮助。

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

具体而言,就此而言:

  

我有两个相同界面的实现,只有一个不同   小。在这种情况下,如何最大化代码重用?我无法相信   为了在一个实现中自定义位逻辑,我必须这样做   复制粘贴所有内容,然后在我的代码中修改一小部分。

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

答案 1 :(得分:5)

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句柄类型。该句柄的用户可以调用这些方法,而无需知道它们是否在事务的上下文中被调用。