只是让我了解 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!"
扩展 - 将指定模块的方法和常量添加到目标的元类(即单例类),例如
如果你调用 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
你说的是对的。然而,它的意义远不止于此。
如果您有一个类 Klazz
和模块 Mod
,则在 Klazz
中包含 Mod
可以让 Klazz
的实例访问 Mod
的方法。或者,您可以使用 Mod
扩展 Klazz
,让 class Klazz
访问 Mod
的方法。但您也可以使用 o.extend Mod
扩展任意对象。在这种情况下,即使与 o
具有相同类的所有其他对象都没有,单个对象也会获得 Mod
的方法。
这是正确的。
在幕后,include 实际上是 append_features 的别名,它(来自文档):
如果此模块尚未添加到 aModule 或其祖先之一,则 Ruby 的默认实现是将此模块的常量、方法和模块变量添加到 aModule。
当您将模块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
一个模块,它的方法变成类方法。
所有其他答案都很好,包括挖掘 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 被扩展(复制)到包含类中。
我还想解释一下它的工作机制。如果我说的不对请指正。
当我们使用 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
对不起,我的英语不好。
我遇到了一个非常有用的 article,它比较了在类中使用的 include
、extend
和 prepend
方法:
include
将模块方法作为实例方法添加到类中,而 extend
将模块方法添加为类方法。必须相应地定义被包含或扩展的模块
extend
如何将方法应用为类 或 实例方法,具体取决于利用率。Klass.extend
= 类方法,objekt.extend
= 实例方法。我总是(错误地)假设类方法来自extend
,而实例来自include
。