代码之家  ›  专栏  ›  技术社区  ›  unnamed_addr

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

  •  9
  • unnamed_addr  · 技术社区  · 10 年前

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

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

    以上层次结构就是这样以“空方法”的方式实现的。

    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的实现方式可能是惯用的,但不是更直接了吗?

    1 回复  |  直到 8 年前
        1
  •  8
  •   icza    7 年前

    Go是 not (quite) an object oriented language :它没有类,它 does not have type inheritance ; 但它支持类似的构造 嵌入,嵌入 两者都打开 struct 水平和水平 interface 级别,并且它确实具有 methods .

    Interfaces Go中只有固定的方法集。A型 含蓄地 如果接口的方法集是接口的超集(并没有意图声明),则实现接口。

    如果你想,空方法很好 文件 显式陈述 您的类型确实实现了一个接口(因为它没有明确声明)。公务的 Go FAQ: How can I guarantee my type satisfies an interface?

    type Fooer interface {
        Foo()
        ImplementsFooer()
    }
    

    如果您希望在类型层次结构中有所区别(例如,您不希望对象同时是 Movable Immovable ),它们必须具有不同的方法集( 可移动的 不可移动的 这在其他方法中不存在),因为如果方法集包含相同的方法,其中一个方法的实现也会自动实现另一个方法,因此您可以分配 可移动的 对象类型的变量 不可移动的 .

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

    减少空方法的数量

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

    如果还创建 结构 实施 对于层次结构中的每个类型和每个实现 嵌入 这个 结构 实现更高一个级别,则更高一级的方法集将自动到来,无需进一步的操作:

    对象

    Object 接口和 ObjectImpl 实施:

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

    不可移动的

    不可移动的 接口和 ImmovableImpl 实施:

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

    笔记 不可移动Impl 仅添加 immovable() 方法 object() 是“继承的”。

    建筑物

    Building 实施:

    type Building struct {
        ImmovableImpl // Embed ImmovableImpl struct
    
        // Building-specific other fields may come here
    }
    

    笔记 建筑物 不添加 任何新方法都会自动 不可移动的 对象

    如果“子类型”的数量增加,或者如果接口类型有不止一个“标记”方法(因为所有方法都是“继承的”),那么这种技术的优势就会大大增加。