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

时间:2009-10-31 20:59:40

标签: ruby module

更详细地说,我有一个模块Narf,它为一系列类提供基本功能。具体来说,我想影响继承Enumerable的所有类。我include Narf中的Enumerable

Array是一个默认包含Enumerable的类。然而,它并未受到模块中Narf的延迟包含的影响。

有趣的是,包含后定义的类从Narf获得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

3 个答案:

答案 0 :(得分:3)

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

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

class Array
  include Enumerable
end

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

但是,如果您向现有模块添加新模块,则必须重新包含该模块,以便更新参考。

答案 1 :(得分:3)

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

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

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

虽然这很苛刻。

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

答案 2 :(得分:2)

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

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

在实践中,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!

现在GitHubGemcutter可以获得更多乐趣。