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

Idiomatic way to mimic proper dynamic dispatch in Go

我是从 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 中它打印 "A".

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

你的问题太抽象了,不好回答,希望对你有所帮助。

Re-think 设计从您要解决的实际问题(业务需求等)开始,不要尝试使用 Java 设计在 Go 中解决它。 Go 没有继承,接口是它唯一的多态形式;你不能以任何合理的方式在 Go 中 "mimic dynamic dispatch"。

具体而言,关于此:

I have two implementations of the same interface that differs only a little. How can I maximize code reusage in this case? I can't believe that in order to customize a bit logic in one implementation I have to copy-paste everything and just fix a little part in my code.

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

Go 没有 "classes" 的子类或扩展。嵌入类型的方法使用它们的原始类型接收器。在这种情况下,方法 MegaDoB 中被提升,但在调用时,它在 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()。但是,嵌入它允许其他代码直接调用 m 上的 Do(),而不知道该字段中实际嵌入的是什么类型。另请注意,嵌入接口的实际结果是根据定义嵌入接口的结构也实现了相同的接口。

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