【问题标题】:What happens internally when a module is extended in Ruby?在 Ruby 中扩展模块时内部会发生什么?
【发布时间】:2013-01-27 20:16:02
【问题描述】:
Foo = Module.new

class MyClass
  include Foo
end

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

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

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

【问题讨论】:

  • 内部是什么意思?如果您想要(并且需要)这种级别的详细信息,您可以直接阅读源代码
  • 我不需要实际的 cpp 实现。我正在阅读 abt ruby​​ 对象模型。我确实包含了模块,并且我在上面的问题中指定了它。但我找不到任何明确的扩展模块。我想从理论上知道它是如何实现的。
  • Mike Li 的回答是正确的。

标签: ruby


【解决方案1】:

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

当模块M 包含在类C 中时,会创建一个匿名代理类⟦M′⟧(称为包含类),使其方法表指针指向@987654325 @的方法表。 (常量表和模块变量相同。)⟦M′⟧ 的超类设置为C 的超类,C 的超类设置为⟦M′⟧

另外,如果M 包含其他模块,则递归应用该过程。

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

我觉得the source code of Module#append_features in Rubinius 可读性很强。

【讨论】:

  • 没有冒犯的意思,但这正是我在关于“包含”的问题中指定的内容。我想知道当我们“扩展”一个模块时会发生什么。
【解决方案2】:

我想你问的是Object#extend

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

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

使用示例一:

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() 只是一个快捷方式,它在接收器的 eigenclass 中包含一个模块。


ruby的方法调用简单说明:

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

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

关于eigenclass和方法调用路径的详细解释,请参考这本很棒的书Metaprogramming Ruby

谢谢

【讨论】:

  • 他不是在说Object#include吗?
  • 他说他知道当你加入时会发生什么,但他不清楚扩展。另外,这是正确的。特征类是关键。
【解决方案3】:

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

【讨论】:

    【解决方案4】:

    简而言之,Ruby 的include 方法将模拟继承:它将允许包含类访问被包含模块的实例方法、变量和常量,就好像它们已在包含类本身中定义一样。正如您所提到的,Ruby 通过创建一个匿名代理类(称为 eigenclass 或单例类)来做到这一点,它引用包含的模块,并将其作为直接超类插入到包含类的祖先中。这就是 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
    

    Bar 中扩展Foo 与说是相同的(一直到它在C 中的实现)

    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 也帮助我了解了他们之间的差异。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-11-16
      • 1970-01-01
      • 2011-06-10
      • 2016-12-12
      相关资源
      最近更新 更多