让我了解Ruby元编程。 mixin / modules总是让我困惑。
这是主要区别还是潜伏着更大的龙? e.g。
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!"
答案 0 :(得分:304)
extend - 将指定模块的方法和常量添加到目标的元类(即单例类) 例如
Klazz.extend(Mod)
,现在Klazz有Mod的方法(作为类方法)obj.extend(Mod)
,现在obj有Mod的方法(作为实例方法),但obj.class
的其他实例没有添加这些方法。extend
是一种公共方法include - 默认情况下,它将指定模块的方法混合为目标模块/类中的实例方法。 e.g。
class Klazz; include Mod; end;
,现在Klazz的所有实例都可以访问Mod的方法(作为实例方法)include
是一个私有方法,因为它是从容器类/模块中调用的。 但是,模块经常通过猴子修补include
方法覆盖 included
的行为。这在传统的Rails代码中非常突出。 more details from Yehuda Katz。
有关include
及其默认行为的更多详细信息,假设您运行了以下代码
class Klazz
include Mod
end
@@foo
或@@bar
super
在检查Klazz真正的超类的foo方法之前会检查Mod#foo。有关详细信息,请参阅RubySpec。)。当然,the ruby core documentation总是最适合这些事情的地方。 The RubySpec project也是一个很棒的资源,因为他们准确地记录了这些功能。
答案 1 :(得分:231)
你所说的是对的。但是还有更多的东西。
如果您有一个课程Klazz
和模块Mod
,Mod
中包括Klazz
,则会Klazz
Mod
Klazz
方法。或者,您可以使用Mod
扩展Klazz
,让类 Mod
访问o.extend Mod
的方法。但您也可以使用Mod
扩展任意对象。在这种情况下,单个对象获取o
的方法,即使与{{1}}具有相同类的所有其他对象都没有。
答案 2 :(得分:13)
这是正确的。
在幕后,include实际上是 append_features 的别名,(来自文档):
Ruby的默认实现是 添加常量,方法和模块 这个模块的变量为aModule if 此模块尚未添加 到aModule或其祖先之一。
答案 3 :(得分: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()功能可让您直接重写包含类。关注内的模块ClassMethods将扩展(复制)到包含类中。
答案 4 :(得分:2)
当您 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
一个模块时,其方法将成为实例方法,而当我们 {{ 1}} ,它的方法成为类方法。
答案 5 :(得分:1)
我还想解释它的工作原理。如果我不对,请更正。
当我们使用include
时,我们将从我们的类添加到包含一些方法的模块的链接。
class A
include MyMOd
end
a = A.new
a.some_method
对象没有方法,只有clases和模块。
因此,当a
收到消息some_method
时,它会在some_method
的特征类中开始搜索方法a
,然后在A
类中开始,然后链接到{{1}如果有一些类模块(以相反的顺序,最后包括胜利)。
当我们使用A
时,我们将添加到对象的本征类中的模块的链接。
因此,如果我们使用A.new.extend(MyMod),我们将模块的链接添加到A的实例特征类或extend
类。
如果我们使用A.extend(MyMod),我们将添加链接到A(对象,类也是对象)eigenclass a'
。
所以A'
的方法查找路径如下:
a =&gt; a'=&gt;将模块链接到'class =&gt;甲
还有一个更改查找路径的前置方法:
a =&gt; a'=&gt;预先插入的模块A =&gt; A =&gt;包含模块到A
抱歉我的英语不好。