在Ruby中扩展模块时内部会发生什么?

时间:2013-02-12 13:44:51

标签: ruby

Foo = Module.new

class MyClass
  include Foo
end

当一个模块包含在一个类中时,会创建一个匿名代理类并将其设置为MyClass的超类。

MyClass.ancestors => [MyClass, Foo, ...]

但是当模块扩展时,内部会发生什么? Ruby如何处理这个问题?

4 个答案:

答案 0 :(得分:5)

我认为你问的是Object#extend

因此,使用extend,我可以任何模块的方法包含在该对象中。 例如,我有一个名为HelperModule的模块:

module HelperModule
  def foo
    puts "from module helper"
  end
end

用法示例1:

obj = Object.new
obj.extend HelperModule
obj.foo                  # => "from module helper"

用法示例2:

class MyClass
  extend HelperModule
end
MyClass.foo              # => "from module helper"

内部,根据Metaprogramming Ruby

  

Object#extend()只是一个快捷方式,在接收者的本征类中包含一个模块。


ruby​​方法的简要说明叫:

  1. 找到对象的类,看看那里定义的方法是
  2. 找到该类的超类,看看那里定义的方法是

  3. obj 
     |
     | class
     |                     superclass                  superclass
     --->  ObjectClass  --------------> SuperClass1 --------------> SuperClass2 ....
    

    关于本征类和方法调用路径的详细说明,请参考这本很棒的书Metaprogramming Ruby

    由于

答案 1 :(得分:0)

  

当一个模块在Ruby中扩展时,内部会发生什么?

当一个模块M包含在类C中时,会创建一个匿名代理类⟦M′⟧(称为 include class ),以便其方法表指针指向M的方法表。 (对于常量表和模块变量也是如此。)⟦M′⟧的超类设置为C的超类,C的超类设置为⟦M′⟧

此外,如果M包含其他模块,则会以递归方式应用该过程。

实际上,这只是默认行为。 真正发生的是include调用M.append_features(C),您可以通过覆盖该方法来自定义所有这些行为。

我发现the source code of Module#append_features in Rubinius非常易读。

答案 2 :(得分:0)

obj.extend SomeModuleobj.eigenclass.include SomeModule相同(注意:这只是伪代码,但您会明白这一点......)。

答案 3 :(得分:0)

简而言之,Ruby的include方法将模拟继承:它将允许包含类访问包含的模块的实例方法,变量和常量,就好像它们已经在包含类本身中定义一样。 Ruby通过创建一个匿名代理类(称为eigenclass或singleton类)来实现这一点,如前所述,它引用包含的模块,并将其作为直接超类插入到包含类的祖先中。这就是Ruby中所谓的 mixin

但是,使用extend可以与单例类进行更多的交互:

module Foo
  def baz
    'baz'
  end
end

module Blah
  def blah
    'blah'
  end
end

class Bar
  extend Foo
  include Blah
  def self.magic
    'magic'
  end
end

Foo中扩展Bar与在中的实施方式完全相同“

Bar.singleton_class.send( :include, Foo )

这意味着它是Bar的单例类,其中Foo的方法和常量基本上是嵌入的,因此类Bar是其单例类的一个实例,将继承这个新功能作为所谓的“类”方法。在Ruby中,模块和类只能有实例方法,因此创建“类”方法实际上只是在类的单例类中创建一个实例方法,以这种方式继承。

Bar.baz
# => 'baz'
Bar.magic
# => 'magic'
Bar.blah
# => NoMethodError
Bar.new.baz
# => NoMethodError
Bar.new.magic
# => NoMethodError
Bar.new.blah
# => 'blah'

您可以在此处查看includeextend的行为差异。要验证他们关于类祖先的行为,您可以要求Bar及其单例类的祖先,它会向您显示模块BlahBar的直接父级,但是模块Foo是单例类的直接父级。

Bar.ancestors
# => [Bar, Blah, Object, Kernel, BasicObject]
Bar.singleton_class.ancestors
# => [Foo, Class, Module, Object, Kernel, BasicObject]

从这些结果中,您可以看到包含模拟包含类中的继承,以及扩展如何仅包含在单例类中。

您可能会尝试查看this question一段时间后得到的答案;他们在解释includeextend功能的行为方面表现出色。 This article也帮助我了解他们之间的差异。