Ruby 类层次结构中“前置”的行为

Posted

技术标签:

【中文标题】Ruby 类层次结构中“前置”的行为【英文标题】:Behaviour of `prepend` in Ruby class hierarchies 【发布时间】:2017-02-22 03:51:06 【问题描述】:

我有一个类Base,还有两个类DerivedDerived2 继承自Base。它们每个都定义了一个函数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]

【问题讨论】:

【参考方案1】:

为了帮助您理解,有一个方法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]

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

前置会将模块置于该列表的前面。 继承将父链放在子链的末尾(不完全是,但为了简单起见)。 super 只是说“去更远的链中找到我同样的方法”

对于Base,我们有两个foo 的实现——在BaseGenGen 将首先找到,因为该模块是预先添加的。因此在Base 的实例上调用它会调用Gen#foo =[S],它也会向上搜索链(通过super=[from Base] em>。

对于Derived,模块没有前置,我们有继承。因此,第一个找到的实现是在Derived =Sfrom Derivedsuper 将搜索来自Base 的链的其余部分(又名上述段落播放) =[from Base]from Derived.

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


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

【讨论】:

我知道prepend 应该如何工作。但是祖先链并不是你上面打印的。 1.Base.ancestors # =&gt; [Gen, Base, Object, Kernel, BasicObject] 2.Derived.ancestors # =&gt; [Derived, Gen, Base, Object, Kernel, BasicObject] 3.Derived2.ancestors # =&gt; [Derived2, Gen, Base, Object, Kernel, BasicObject]我在 Ruby 2.2.4 上。 你知道这种行为改变是在哪个版本中生效的吗? @CppNoob 我现在会下载 2.2.5,但它似乎在 2.3+ 之后发生了变化。它没有反映在文档中,我也无法在更改日志中找到它。好像是倒退了,我会再做一些调查报告。 @CppNoob,是的 - 它是 2.3.0 @CppNoob,直到 2.3.0,这一直是“正常”行为。 [Gen, Derived2, Gen, Base, Object, Kernel, BasicObject] 版本附带 2.3.0。 documentation 明确指出,如果它已经存在于链中,则不应添加它。所以好像是回归,我创建了a ticket。

以上是关于Ruby 类层次结构中“前置”的行为的主要内容,如果未能解决你的问题,请参考以下文章

腌制一个类时,我在 python 中的行为与在 cython 中的行为不同

Bootstrap 折叠复杂行为

设计模式——桥接模式

访问者模式

接口和抽象类

设计模式 - 桥接模式详解