ChatGPT解决这个技术问题 Extra ChatGPT

Ruby中的include和extend有什么区别?

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

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

扩展:在目标类中混入指定的模块方法作为类方法

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

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

J
John Douthat

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

如果你调用 Klazz.extend(Mod),现在 Klazz 有 Mod 的方法(作为类方法)

如果您调用 obj.extend(Mod),现在 obj 具有 Mod 的方法(作为实例方法),但 obj.class 的其他实例没有添加这些方法。

扩展是一个公共方法

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

如果您调用 Klazz 类;包括模组; 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 将在之前检查 Mod#foo检查 Klazz 的真正超类的 foo 方法。有关详细信息,请参阅 RubySpec。)。

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

#include RubySpec rubydoc

#included RubySpec rubydoc

#extend RubySpec rubydoc

#extended RubySpec rubydoc

#extend_object RubySpec rubydoc

#append_features RubySpec rubydoc


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

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

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


像孔子一样简洁。
T
Toby Hede

这是正确的。

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

如果此模块尚未添加到 aModule 或其祖先之一,则 Ruby 的默认实现是将此模块的常量、方法和模块变量添加到 aModule。


C
Chintan

当您将模块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一个模块,它的方法变成类方法


H
Ho-Sheng Hsiao

所有其他答案都很好,包括挖掘 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,则会引用方法、常量、类、子模块和其他声明。

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

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


u
user1136228

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

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

class A
include MyMOd
end

a = A.new
a.some_method

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

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

所以 a 的方法查找路径如下:a => a' =>将模块链接到一个'类=>一个。

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

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

对不起,我的英语不好。


A
Abdullah Numan

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

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