Ruby:类C包含模块M;在M中包含模块N不会影响C. [英] Ruby: class C includes module M; including module N in M does not affect C. What gives?
问题描述
更详细地说,我有一个模块Narf
,它为一系列类提供了基本功能.具体来说,我想影响所有继承Enumerable
的类.所以我在Enumerable
中是include Narf
.
More verbosely, I have a module Narf
, which provides essential features to a range of classes. Specifically, I want to affect all classes that inherit Enumerable
. So I include Narf
in Enumerable
.
Array
是默认情况下包含Enumerable
的类.但是,它不受模块中后来加入Narf
的影响.
Array
is a class that includes Enumerable
by default. Yet, it is not affected by the late inclusion of Narf
in the module.
有趣的是,包含之后定义的类从Enumerable
中获取Narf
.
Interestingly, classes defined after the inclusion get Narf
from Enumerable
.
# This module provides essential features
module Narf
def narf?
puts "(from #{self.class}) ZORT!"
end
end
# I want all Enumerables to be able to Narf
module Enumerable
include Narf
end
# Fjord is an Enumerable defined *after* including Narf in Enumerable
class Fjord
include Enumerable
end
p Enumerable.ancestors # Notice that Narf *is* there
p Fjord.ancestors # Notice that Narf *is* here too
p Array.ancestors # But, grr, not here
# => [Enumerable, Narf]
# => [Fjord, Enumerable, Narf, Object, Kernel]
# => [Array, Enumerable, Object, Kernel]
Fjord.new.narf? # And this will print fine
Array.new.narf? # And this one will raise
# => (from Fjord) ZORT!
# => NoMethodError: undefined method `narf?' for []:Array
推荐答案
在写我的问题时,我不可避免地遇到一个答案.这是我想出的.让我知道是否错过了一个显而易见的,简单得多的解决方案.
In writing my question, inevitably, I came across an answer. Here's what I came up with. Let me know if I missed an obvious, much simpler solution.
问题似乎在于,模块包含会弄平所包含模块的祖先,并包含 that .因此,方法查找不是完全动态的,所包含模块的祖先链也不会被检查.
The problem seems to be that a module inclusion flattens the ancestors of the included module, and includes that. Thus, method lookup is not fully dynamic, the ancestor chain of included modules is never inspected.
在实践中,Array
知道Enumerable
是祖先,但是它并不关心Enumerable
中当前包含的内容.
In practice, Array
knows Enumerable
is an ancestor, but it doesn't care about what's currently included in Enumerable
.
好处是您可以再次include
模块,它将重新计算模块祖先链,并包括整个内容.因此,在定义并包含Narf
之后,您可以重新打开Array
并再次包含Enumerable
,它也会得到Narf
.
The good thing is that you can include
modules again, and it'll recompute the module ancestor chain, and include the entire thing. So, after defining and including Narf
, you can reopen Array
and include Enumerable
again, and it'll get Narf
too.
class Array
include Enumerable
end
p Array.ancestors
# => [Array, Enumerable, Narf, Object, Kernel]
现在让我们概括一下:
# Narf here again just to make this example self-contained
module Narf
def narf?
puts "(from #{self.class}) ZORT!"
end
end
# THIS IS THE IMPORTANT BIT
# Imbue provices the magic we need
class Module
def imbue m
include m
# now that self includes m, find classes that previously
# included self and include it again, so as to cause them
# to also include m
ObjectSpace.each_object(Class) do |k|
k.send :include, self if k.include? self
end
end
end
# imbue will force Narf down on every existing Enumerable
module Enumerable
imbue Narf
end
# Behold!
p Array.ancestors
Array.new.narf?
# => [Array, Enumerable, Narf, Object, Kernel]
# => (from Array) ZORT!
现在在 GitHub 和 Gemcutter 带来额外的乐趣.
Now on GitHub and Gemcutter for extra fun.
这篇关于Ruby:类C包含模块M;在M中包含模块N不会影响C.的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!