Ruby class 层次结构中“prepend”的行为
Behaviour of `prepend` in Ruby class hierarchies
我有一个 class Base
,还有两个 class 继承自 Base
的 Derived
和 Derived2
。它们每个都定义了一个函数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
的实现 - 在 Base
和 Gen
中。 Gen
将首先被找到,因为模块是前置的。因此在 Base
的实例上调用它会调用 Gen#foo
=[S]
,它也会向上搜索链(通过 super
) =[from Base]
.
对于Derived
,该模块没有前置,我们有继承。因此,第一个发现的实现是 Derived
=Sfrom Derived
和 super
将搜索来自 [=13= 的链的其余部分](也就是上段打完了)=[from Base]from Derived
.
对于Derived2
,模块是前置的,所以那里的方法会先找到=[S]
。然后那里的super
会在Derived2
=[Sfrom Derived]
中找到下一个foo
,然后那里的super
就会播放再次解决 Base
的情况 =[[from Base]from Derived]
.
编辑: 似乎直到最近,prepend
才会首先在祖先链中搜索并仅在模块不存在时才添加模块(类似地include
)。更令人困惑的是,如果您首先创建父级,从它继承,在子级中添加前缀,然后在父级中添加,您将得到较新版本的结果。
我有一个 class Base
,还有两个 class 继承自 Base
的 Derived
和 Derived2
。它们每个都定义了一个函数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
的实现 - 在 Base
和 Gen
中。 Gen
将首先被找到,因为模块是前置的。因此在 Base
的实例上调用它会调用 Gen#foo
=[S]
,它也会向上搜索链(通过 super
) =[from Base]
.
对于Derived
,该模块没有前置,我们有继承。因此,第一个发现的实现是 Derived
=Sfrom Derived
和 super
将搜索来自 [=13= 的链的其余部分](也就是上段打完了)=[from Base]from Derived
.
对于Derived2
,模块是前置的,所以那里的方法会先找到=[S]
。然后那里的super
会在Derived2
=[Sfrom Derived]
中找到下一个foo
,然后那里的super
就会播放再次解决 Base
的情况 =[[from Base]from Derived]
.
编辑: 似乎直到最近,prepend
才会首先在祖先链中搜索并仅在模块不存在时才添加模块(类似地include
)。更令人困惑的是,如果您首先创建父级,从它继承,在子级中添加前缀,然后在父级中添加,您将得到较新版本的结果。