Go 中创建复杂结构层次结构的惯用方法是什么?

What is the idiomatic way in Go to create a complex hierarchy of structs?

我正在用 Go 编写一个解释器,我正在寻找存储 AST 的惯用方式。我阅读了 Go 编译器源代码,似乎他们使用带有空方法的接口来表示 AST。例如,我们有以下层次结构,

Object
--Immovable
----Building
----Mountain
--Movable
----Car
----Bike

这就是上述层次结构在 "empty method" 中的实现方式。

type Object interface {
  object()
}

type Immovable interface {
  Object
  immovable()
}

type Building struct {
  ... 
}

type Mountain struct {
  ... 
}

type Movable interface {
  Object
  movable()
}

type Car struct {
  ...
} 

type Mountain struct {
  ...
} 

func (*Building) object() {}
func (*Mountain) object() {}
func (*Car) object() {}
func (*Bike) object() {}
func (*Building) immovable() {}
func (*Mountain) immovable() {}
func (*Car) movable() {}
func (*Bike) movable() {}    

上面的代码是一个人为的例子,这就是 Go 编译器 implemented AST 与许多空方法的方式。但为什么?注意定义了多少个空方法。随着层级深度的增加,它可能会变得非常复杂。

评论中指出,空方法不允许分配不兼容的类型。例如,在我们的示例中,*Car 不能分配给 *Immovable

这在其他支持继承的语言(如 C++)中非常容易。我想不出任何其他表示 AST 的方法。

Go 编译器 AST 的实现方式可能是惯用的,但不是那么直接吗?

Go 是 not (quite) an object oriented language: it does not have classes and it does not have type inheritance; but it supports a similar construct called embedding both on struct level and on interface level, and it does have methods.

Go 中的

Interfaces 只是固定的方法集。如果类型的方法集是接口的超集(没有意图声明),则类型 隐式 实现接口。

如果您想记录明确声明您的类型确实实现了接口(因为没有说明明确)。官方Go FAQ: How can I guarantee my type satisfies an interface?

type Fooer interface {
    Foo()
    ImplementsFooer()
}

如果你想在你的类型层次结构中有所区别(例如,你不想让一个对象既是 Movable 又是 Immovable),它们必须有不同的方法集(必须有MovableImmovable 的每个方法集中至少有 1 个方法不存在于另一个方法集中),因为如果方法集包含相同的方法,其中一个的实现将自动实现另一个也因此您可以将 Movable 对象分配给类型为 Immovable.

的变量

将空方法添加到具有相同名称的接口将为您提供这种区别,前提是您不会将此类方法添加到其他类型。

减少空方法的数量

就我个人而言,我对空方法没有任何问题。不过有一种方法可以减少它们。

如果您还为层次结构中的每个类型和每个实现创建一个 struct 实现 嵌入 struct实现上一层,上一层的方法集自动来,不用多说:

对象

Object 接口和 ObjectImpl 实现:

type Object interface {
  object()
}
type ObjectImpl struct {}
func (o *ObjectImpl) object() {}

不动产

Immovable 接口和 ImmovableImpl 实现:

type Immovable interface {
    Object
    immovable()
}
type ImmovableImpl struct {
    ObjectImpl // Embed ObjectImpl
}
func (o *Immovable) immovable() {}

注意ImmovableImpl只增加了immovable()方法,object()是"inherited".

建筑物

Building 实施:

type Building struct {
    ImmovableImpl // Embed ImmovableImpl struct

    // Building-specific other fields may come here
}

注意Building 不添加任何新方法,但它自动成为一个Immovable对象。

如果 "subtypes" 的数量增加,或者如果接口类型有不止 1 个 "marker" 方法(因为所有方法都是 "inherited"),这种技术的优势会大大增加。