我刚刚了解 Ruby 元编程。mixin/模块总是让我感到困惑。

  • 包括: :混合指定的模块方法为 实例方法 在目标类中
  • 延长: :混合指定的模块方法为 类方法 在目标类中

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

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!"
有帮助吗?

解决方案

你说的是正确的。然而,事情还不止于此。

如果你有课 Klazz 和模块 Mod, , 包括 ModKlazz 给出实例 Klazz 进入 Mod的方法。或者你可以延长 KlazzMod 给予 班级 Klazz 进入 Mod的方法。但你也可以扩展任意对象 o.extend Mod. 。在这种情况下,单个对象得到 Mod的方法,即使所有其他对象具有相同的类 o 不要。

其他提示

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

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

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

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

然而, ,模块经常 覆盖 include通过猴子修补的行为 included 方法。这在遗留 Rails 代码中非常突出。 耶胡达·卡茨的更多细节.

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

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

当然, ruby 核心文档 永远是获得这些东西的最佳地点。 RubySpec 项目 这也是一个很棒的资源,因为他们精确地记录了功能。

这是正确的。

在幕后, include 实际上是一个别名 追加特征, ,其中(来自文档):

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

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

至于用例:

如果你 包括 类 ClassThatInincludes 中的模块 ReusableModule 会引用方法、常量、类、子模块和其他声明。

如果你 延长 类 ClassThatExtends 与模块 ReusableModule,然后方法和常量获取 复制的. 。显然,如果您不小心,动态复制定义可能会浪费大量内存。

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

我还想解释一下其运作机制。如果我说得不对请指正。

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

class A
include MyMOd
end

a = A.new
a.some_method

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

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

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

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

a => a' => A 的前置模块 => A => A 包含的模块

对不起,我的英语不好。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top