【问题标题】:What is the difference between include and extend in Ruby?Ruby中的include和extend有什么区别?
【发布时间】:2023-03-26 12:11:01
【问题描述】:

刚刚开始了解 Ruby 元编程。 mixin/modules 总是让我感到困惑。

  • include:在目标类中混入指定的模块方法作为实例方法
  • extend:在目标类中混入指定的模块方法作为类方法

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

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!"

【问题讨论】:

标签: ruby module include extend


【解决方案1】:

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

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

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

  • 如果您调用 class Klazz; include Mod; end;,现在所有 Klazz 实例都可以访问 Mod 的方法(作为实例方法)
  • include 是一个私有方法,因为它旨在从容器类/模块中调用。

然而,模块经常覆盖 include 的行为通过猴子修补included 方法。这在遗留 Rails 代码中非常突出。 more details from Yehuda Katz

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

class Klazz
  include Mod
end
  • 如果 Mod 已包含在 Klazz 或其祖先之一中,则 include 语句无效
  • 它还包括 Mod 在 Klazz 中的常量,只要它们不冲突
  • 它使 Klazz 可以访问 Mod 的模块变量,例如@@foo@@bar
  • 如果存在循环包含,则引发 ArgumentError
  • 将模块附加为调用者的直接祖先(即,它将Mod添加到Klazz.ancestors,但Mod没有添加到Klazz.superclass.superclass.superclass的链中。因此,在Klazz#foo中调用super会检查在检查 Klazz 的真正超类的 foo 方法之前,用于 Mod#foo。有关详细信息,请参阅 RubySpec。)。

当然,the ruby core documentation 始终是购买这些东西的最佳去处。 The RubySpec project 也是一个很棒的资源,因为他们准确地记录了功能。

【讨论】:

  • 我知道这是很老的帖子,但回复的清晰性无法阻止我发表评论。非常感谢您的精彩解释。
  • @anwar 显然,但现在我可以发表评论,并且我设法再次找到了这篇文章。它可以在这里找到:aaronlasseigne.com/2012/01/17/explaining-include-and-extend,我仍然认为架构使理解更容易
  • 这个响应中最大的胜利是extend 可以将方法应用为类 实例方法,具体取决于利用率。 Klass.extend = 类方法,objekt.extend = 实例方法。我总是(错误地)假设类方法来自extend,实例来自include
【解决方案2】:

你说的是对的。然而,它的意义远不止于此。

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

【讨论】:

  • 像孔子一样简洁。
【解决方案3】:

没错。

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

Ruby 的默认实现是 添加常量、方法和模块 这个模块的变量到 aModule if 此模块尚未添加 到 aModule 或其祖先之一。

【讨论】:

    【解决方案4】:

    当您将一个模块include 导入到一个类中时,模块方法将作为实例方法 导入。

    但是,当您将一个模块extend 导入到一个类中时,模块方法将作为类方法导入。

    例如,如果我们有一个模块Module_test 定义如下:

    module Module_test
      def func
        puts "M - in module"
      end
    end
    

    现在,对于 include 模块。如果我们定义类A如下:

    class A
      include Module_test
    end
    
    a = A.new
    a.func
    

    输出将是:M - in module

    如果我们将行 include Module_test 替换为 extend Module_test 并再次运行代码,我们会收到以下错误:undefined method 'func' for #<A:instance_num> (NoMethodError)

    将方法调用a.func改为A.func,输出变为:M - in module

    从上面的代码执行可以看出,当我们include一个模块时,它的方法变成instance methods,而当我们extend 一个模块,它的方法变成类方法

    【讨论】:

      【解决方案5】:

      所有其他答案都很好,包括挖掘 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

      关于用例:

      如果您在 ClassThatIncludes 类中包含模块 ReusableModule,则方法、常量、类、子模块和其他声明将被引用。

      如果您扩展类 ClassThatExtends 使用模块 ReusableModule,则方法和常量将被复制。显然,如果不小心,动态复制定义会浪费大量内存。

      如果您使用 ActiveSupport::Concern,.included() 功能可以让您直接重写包含类。 Concern 中的模块 ClassMethods 被扩展(复制)到包含类中。

      【讨论】:

        【解决方案6】:

        我还想解释一下它的工作机制。如果我说的不对,请指正。

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

        class A
        include MyMOd
        end
        
        a = A.new
        a.some_method
        

        对象没有方法,只有类和模块有。 因此,当a 接收到消息some_method 时,它会在a 的特征类中开始搜索方法some_method,然后在A 类中,然后在链接到A 类模块(如果有的话)中(以相反的顺序, 最后包含的获胜)。

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

        所以a的方法查找路径如下: a => a' => 将模块链接到 a' class=> A.

        还有一个 prepend 方法可以改变查找路径:

        a => a' => 将模块添加到 A => A => 包含模块到 A

        抱歉我的英语不好。

        【讨论】:

          【解决方案7】:

          我遇到了一个非常有用的article,它比较了类中使用的includeextendprepend 方法

          include 将模块方法作为实例方法添加到类中,而extend 将模块方法添加为类方法。包含或扩展的模块必须相应定义

          【讨论】:

            猜你喜欢
            • 2010-09-24
            • 1970-01-01
            • 2015-05-15
            • 2010-09-06
            • 1970-01-01
            • 1970-01-01
            • 2011-11-22
            • 2010-12-23
            • 1970-01-01
            相关资源
            最近更新 更多