Ruby 类层次结构中“前置”的行为
Posted
技术标签:
【中文标题】Ruby 类层次结构中“前置”的行为【英文标题】:Behaviour of `prepend` in Ruby class hierarchies 【发布时间】:2017-02-22 03:51:06 【问题描述】:我有一个类Base
,还有两个类Derived
和Derived2
继承自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
的实现——在Base
和Gen
。 Gen
将首先找到,因为该模块是预先添加的。因此在Base
的实例上调用它会调用Gen#foo
=[S]
,它也会向上搜索链(通过super
)=[from Base]
em>。
对于Derived
,模块没有前置,我们有继承。因此,第一个找到的实现是在Derived
=Sfrom Derived
和super
将搜索来自Base
的链的其余部分(又名上述段落播放) =[from Base]from Derived
.
对于Derived2
,模块是前置的,所以会先找到那里的方法=[S]
。然后那里的super
会在Derived2
中找到下一个foo
=[Sfrom Derived]
,而那里的super
将再次播放Base
的情况=@ 987654350@.
编辑:似乎直到最近,prepend
才会首先在祖先链中搜索并仅在模块不存在时才添加该模块(类似于include
)。更令人困惑的是,如果您首先创建父级,从其继承,在子级中添加前缀,然后在父级中添加前缀,您将获得较新版本的结果。
【讨论】:
我知道prepend
应该如何工作。但是祖先链并不是你上面打印的。 1.Base.ancestors # => [Gen, Base, Object, Kernel, BasicObject]
2.Derived.ancestors # => [Derived, Gen, Base, Object, Kernel, BasicObject]
3.Derived2.ancestors # => [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 类层次结构中“前置”的行为的主要内容,如果未能解决你的问题,请参考以下文章