457

只是让我了解 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!"
4

7 回答 7

368

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

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

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

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

但是,模块经常通过猴子修补方法来覆盖的行为。 这在遗留 Rails 代码中非常突出。来自 Yehuda Katz 的更多详细信息includeincluded

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

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

当然,ruby 核心文档始终是这些东西的最佳去处。RubySpec 项目也是一个很棒的资源,因为他们准确地记录了功能。

于 2011-02-15T19:10:25.460 回答
273

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

如果你有一个 classKlazz和 module Mod,包括ModinKlazz提供Klazz访问Mod's 方法的实例。或者您可以Klazz通过Mod授予 Klazz访问Mod's 方法的权限进行扩展。但您也可以使用 . 扩展任意对象o.extend Mod。在这种情况下,Mod即使具有相同类的所有其他对象o都没有,单个对象也会获得 's 方法。

于 2008-10-01T09:59:38.203 回答
17

这是正确的。

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

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

于 2008-10-01T08:08:30.777 回答
12

当您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_testextend Module_test再次运行代码,我们会收到以下错误:undefined method 'func' for #<A:instance_num> (NoMethodError).

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

从上面的代码执行中可以清楚地看出,当我们include是一个模块时,它的方法变成了实例方法,当我们extend是一个模块时,它的方法变成了类方法

于 2019-09-20T05:40:33.623 回答
3

所有其他答案都很好,包括挖掘 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 被扩展(复制)到包含类中。

于 2011-09-22T21:02:06.940 回答
2

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

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

class A
include MyMOd
end

a = A.new
a.some_method

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

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

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

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

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

对不起,我的英语不好。

于 2016-03-27T12:11:02.673 回答
0

我遇到了一篇非常有用的文章,它比较了类include中使用extendprepend方法:

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

于 2021-11-26T12:25:58.633 回答