Ruby:C类包含模块M; 包括M中的模块N不影响C.什么给出?

更详细地说,我有一个模块Narf ,它为一系列类提供了基本function。 具体来说,我想影响inheritanceEnumerable所有类。 所以我把include NarfEnumerable

Array是一个默认包含Enumerable的类。 然而,它并没有受到Narf在模块中的后期加入的影响。

有趣的是,在包含之后定义的类从Enumerable获得Narf

例:

 # 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 

您会想到两个解决问题的方法。 他们都不是真的很漂亮:

a)浏览包含Enumerable的所有类,并使它们也包括Narf。 像这样的东西:

 ObjectSpace.each(Module) do |m| m.send(:include, Narf) if m < Enumerable end 

这虽然很狡猾。

b)直接将function添加到Enumerable而不是自己的模块。 这可能实际上是可以的,它会起作用。 这是我推荐的方法,虽然它也不完美。

class Array已与Enumerable模块混合使用,该模块尚未包含Narf模块。 这就是它抛出(基本上是它的方法)n错误的原因。

如果你再次在数组中包含Enumerable,即。

 class Array include Enumerable end 

混合使得从类到包含模块的引用,在该特定对象空间中具有要包括的所有方法。 如果修改模块的任何现有方法,则包含该模块的所有类都将反映更改。

但是,如果向现有模块添加新模块,则必须重新包含模块,以便可以更新引用。

在写我的问题时,我不可避免地遇到了一个答案。 这就是我想出的。 如果我错过了一个明显的,更简单的解决方案,请告诉我。

问题似乎是模块包含使所包含模块的祖先变平,并包括 。 因此,方法查找不是完全动态的,从不检查包含模块的祖先链。

在实践中, Array知道Enumerable是一个祖先,但它并不关心Enumerable当前包含的内容。

好处是你可以再次include模块,它将重新计算模块的祖先链,并包含整个事物。 所以,在定义并包含Narf ,你可以重新打开Array并再次包含Enumerable ,它也会得到Narf

 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上获得额外的乐趣。