关于模块:Ruby中包含和扩展有什么区别?

What is the difference between include and extend in Ruby?

只是想了解一下Ruby元编程。混音/模块总是让我困惑。

  • 包括:将指定的模块方法混合为目标类中的实例方法
  • 扩展:将指定的模块方法混合为目标类中的类方法
  • 小精灵

    那么主要的区别是这一点还是一条更大的龙潜伏着呢?例如

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    module ReusableModule
      def module_method
        puts"Module Method: Hi there!"
      end
    end

    class ClassThatIncludes
      include ReusableModule
    end
    class ClassThatExtends
      extend ReusableModule
    end

    puts"Include"
    ClassThatIncludes.new.module_method       #"Module Method: Hi there!"
    puts"Extend"
    ClassThatExtends.module_method            #"Module Method: Hi there!"


    extend-将指定模块的方法和常量添加到目标的元类(即singleton类)中。例如

    • 如果您调用Klazz.extend(Mod),现在klazz有mod的方法(作为类方法)
    • 如果您调用obj.extend(Mod),现在obj有mod的方法(作为实例方法),但是没有其他obj.class的实例添加这些方法。
    • extend是一种公共方法。
    • 小精灵

      include-默认情况下,它将指定模块的方法混合为目标模块/类中的实例方法。例如

      • 如果调用class Klazz; include Mod; end;,那么现在klazz的所有实例都可以访问mod的方法(作为实例方法)
      • include是一个私有方法,因为它是从容器类/模块内部调用的。
      • 小精灵

        然而,模块经常通过猴子修补included方法来覆盖include的行为。这在传统的Rails代码中非常突出。更多的细节来自Yehuda Katz。

        更多关于include及其默认行为的详细信息,假设您已经运行了以下代码

        1
        2
        3
        class Klazz
          include Mod
        end
        • 如果mod已经包含在klazz或它的祖先中,那么include语句将不起作用。
        • 它还包括了国防部在克拉兹的常量,只要它们不冲突
        • 它允许Klazz访问mod的模块变量,例如@@foo@@bar
        • 如果存在循环包含,则引发ArgumentError
        • 将模块作为调用方的直接祖先(即,它将mod添加到klazz.precements,但mod不添加到klazz.superclass.superclass.superclass的链中。因此,在Klazz Foo中调用super将检查mod Foo,然后检查Klazz真正的超类的Foo方法。详情见Rubyspec)。
        • 小精灵

          当然,Ruby核心文档始终是处理这些问题的最佳位置。Rubyspec项目也是一个很棒的资源,因为它们精确地记录了功能。

          • #includeRubyspec RubyDoc公司
          • #includedRubyspec RubyDoc公司
          • #extendRubyspec RubyDoc公司
          • #extendedRubyspec RubyDoc公司
          • #extend_objectRubyspec RubyDoc公司
          • #append_featuresRubyspec RubyDoc公司
          • 小精灵


            你说的是对的。然而,还有更多的事情要做。

            如果您有一个类Klazz和模块Mod,包括Klazz中的Mod提供了Klazz访问Mod方法的实例。也可以使用Mod扩展Klazz,使类Klazz可以访问Mod的方法。但也可以使用o.extend Mod扩展任意对象。在这种情况下,单个对象获得Mod的方法,即使所有其他与o具有相同类的对象都没有。


            没错。

            在幕后,include实际上是附加功能的别名,它(来自文档):

            Ruby's default implementation is to
            add the constants, methods, and module
            variables of this module to aModule if
            this module has not already been added
            to aModule or one of its ancestors.


            所有其他答案都很好,包括挖掘rubyspecs的提示:

            https://github.com/rubyspec/rubyspec/blob/master/core/module/include_spec.rb

            https://github.com/rubyspec/rubyspec/blob/master/core/module/extend_object_spec.rb

            至于用例:

            如果在包含的类类类中包含模块可重用模块,则将引用方法、常量、类、子模块和其他声明。

            如果扩展使用模块可重用模块扩展的类类,那么方法和常量将被复制。显然,如果不小心,您可以通过动态复制定义来浪费大量内存。

            如果使用activesupport::concern,.included()功能可以直接重写include类。关注点内的模块类方法被扩展(复制)到include类中。


            我还想解释一下这个机制的工作原理。如果我不对,请纠正。

            当我们使用include时,我们正在添加一个从类到包含一些方法的模块的链接。

            1
            2
            3
            4
            5
            6
            class A
            include MyMOd
            end

            a = A.new
            a.some_method

            对象没有方法,只有类和模块有。因此,当a接收到mesage some_method时,它开始在a的本征类中搜索方法some_method,然后在a类中搜索方法,如果有一些(相反的顺序,最后一个包含的赢),然后在链接到a类模块中搜索方法some_method

            当我们使用extend时,我们正在向对象的本征类中的模块添加链接。因此,如果我们使用一个.new.extend(mymod),我们将向我们的模块添加到一个实例eigen类或a'类的链接。如果我们使用a.extend(mymod),我们将向(对象,类也是对象)特征类a'添加链接。

            因此,a的方法查找路径如下:A=>A'=>将模块链接到"类=>A"。

            此外,还有一个预发送方法,用于更改查找路径:

            A=>A'=>预装模块至A=>A=>包含模块至A

            抱歉我英语不好。