Ruby中include和extend的区别是什么?

我只是在了解Ruby元编程。mixin/modules总是把我弄糊涂。

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

所以主要的区别只是这个还是有更大的龙潜伏着? 例如< / p >

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!"
103786 次浏览

这是正确的。

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

Ruby的默认实现是to 添加常量、方法和模块 将该模块的变量转换为aModule if 此模块尚未添加 到aModule或其祖先之一。

你说得对。然而,事情远不止如此。

如果你有一个类Klazz和模块Mod,在Klazz中包含Mod可以让Klazz实例访问Mod的方法。或者你可以用Mod扩展Klazz,让Mod3 Klazz访问Mod的方法。但是你也可以用Mod0扩展任意对象。在这种情况下,单个对象获得Mod的方法,尽管与Mod2具有相同类的所有其他对象都没有。

扩展 -将指定模块的方法和常量添加到目标的元类(即单例类) 例如< / p >
  • 如果你调用Klazz.extend(Mod),现在Klazz有Mod的方法(作为类方法)
  • 如果你调用obj.extend(Mod),现在obj有Mod的方法(作为实例方法),但没有其他obj.class的实例添加这些方法。
  • extend是一个公共方法

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

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

然而,模块经常通过对included方法进行猴子补丁来改变覆盖 include的行为。这在遗留Rails代码中非常突出。Yehuda Katz报道

关于include的更多细节,以及它的默认行为,假设你已经运行了以下代码

class Klazz
include Mod
end
  • 如果Mod已经包含在Klazz或其祖先之一中,则include语句无效
  • 它还包括《Klazz》中的Mod常量,只要它们不冲突
  • 它允许Klazz访问Mod的模块变量,例如@@foo@@bar
  • 如果有循环包含则引发ArgumentError
  • 附加模块作为调用者的直接祖先(例如,它将Mod添加到Klazz。但是Mod没有被添加到klaz .superclass.superclass.superclass. superclass的链中。因此,在Klazz#foo中调用super将在检查Klazz的真正超类的foo方法之前检查Mod#foo。详见RubySpec)。

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

  • __abc0 __abc1 __abc2
  • __abc0 __abc1 __abc2
  • __abc0 __abc1 __abc2
  • __abc0 __abc1 __abc2
  • __abc0 __abc1 __abc2
  • __abc0 __abc1 __abc2

所有其他的答案都很好,包括挖掘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()函数可以让你直接重写include类。关注点中的ClassMethods模块将扩展(复制)到包含类中。

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

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

class A
include MyMOd
end


a = A.new
a.some_method
对象没有方法,只有类和模块有。 因此,当a接收到消息some_method时,它开始在a的特征类中搜索方法some_method,然后在A类中搜索,然后在链接到A类模块(如果有的话)中搜索(倒序,最后包含的是wins) 当我们使用extend时,我们是在对象的特征类中为一个模块添加链接。 因此,如果我们使用A.new.extend(MyMod),我们是在将我们的模块链接到A的实例特征类或a'类。 如果我们使用A.extend(MyMod),我们将链接到A(对象的,类也是对象)eigenclass A'.

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

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

a => a' =>前置模块到a => a =>包含模块到a

对不起,我的英语不好。

当你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

如果我们用extend Module_test替换行include Module_test并再次运行代码,我们会收到以下错误:undefined method 'func' for #<A:instance_num> (NoMethodError)

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

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

我遇到了一个非常有用的文章,它比较了includeextendprepend使用的在类内部方法:

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