Ruby 中的包含和扩展有什么区别?
题
我刚刚了解 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
, , 包括 Mod
在 Klazz
给出实例 Klazz
进入 Mod
的方法。或者你可以延长 Klazz
和 Mod
给予 班级 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_method
在 a
的本征类,然后在 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 包含的模块
对不起,我的英语不好。