Ruby class 层次结构中“prepend”的行为

Behaviour of `prepend` in Ruby class hierarchies

我有一个 class Base,还有两个 class 继承自 BaseDerivedDerived2。它们每个都定义了一个函数foo

我还有一个模块 Gen,它是 prepend-ed 到 Base。它也是 prepend-ed 到 Derived2 但不是 Derived.

当我在 Derived2 的实例上调用 foo 时,结果就好像 Gen 模块只是 prepend-ed 到 Base而不是 Derived2 也。这是预期的行为吗?

以上场景的代码如下:

module Gen
  def foo
    val = super
    '[' + val + ']'
  end
end

class Base
  prepend Gen

  def foo
    "from Base"
  end
end

class Derived < Base
  def foo
    val = super
    val + "from Derived"
  end
end

class Derived2 < Base
  prepend Gen
  def foo
    val = super
    val + "from Derived"
  end
end

Base.new.foo     # => "[from Base]"

Derived.new.foo  # => "[from Base]from Derived"

Derived2.new.foo # => "[from Base]from Derived"

我希望上面语句的最后输出:

[[from Base]from Derived]

为了帮助您理解,有一个方法Class#ancestors,它告诉您搜索方法的顺序。在这种情况下:

Base.ancestors     # => [Gen, Base, Object, Kernel, BasicObject]
Derived.ancestors  # => [Derived, Gen, Base, Object, Kernel, BasicObject]
Derived2.ancestors # => [Gen, Derived2, Gen, Base, Object, Kernel, BasicObject]

因此,当您在作为上述 class 实例的对象上调用方法时,将按该顺序在相应列表中搜索该方法。

  • 前置将一个模块放在该列表的前面。
  • 继承将父链放在子链的末尾(不完全是,但为了简单起见)。
  • super 只是说 "go traverse the chain further and find me the same method".

对于 Base,我们有两个 foo 的实现 - 在 BaseGen 中。 Gen 将首先被找到,因为模块是前置的。因此在 Base 的实例上调用它会调用 Gen#foo =[S],它也会向上搜索链(通过 super) =[from Base].

对于Derived,该模块没有前置,我们有继承。因此,第一个发现的实现是 Derived =Sfrom Derivedsuper 将搜索来自 [=13= 的链的其余部分](也就是上段打完了)=[from Base]from Derived.

对于Derived2,模块是前置的,所以那里的方法会先找到=[S]。然后那里的super会在Derived2=[Sfrom Derived]中找到下一个foo,然后那里的super就会播放再次解决 Base 的情况 =[[from Base]from Derived].


编辑: 似乎直到最近,prepend 才会首先在祖先链中搜索并仅在模块不存在时才添加模块(类似地include)。更令人困惑的是,如果您首先创建父级,从它继承,在子级中添加前缀,然后在父级中添加,您将得到较新版本的结果。