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


414

只是让我了解Ruby元编程。mixin /模块总是设法使我感到困惑。

  • 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!"

Answers:


249

你说的是对的。但是,还有更多的东西。

如果您有一个类Klazz和模块Mod,则Mod在中包含Klazz提供KlazzMod的方法的访问实例。或者,您可以扩展KlazzMod允许 Klazz访问Mod的方法。但您也可以使用扩展任意对象o.extend Mod。在这种情况下,Mod即使所有其他具有相同类的对象o都不是,单个对象也将获得的方法。


324

扩展 -将指定模块的方法和常量添加到目标的元类(即单例类),例如

  • 如果您调用Klazz.extend(Mod),则Klazz现在具有Mod的方法(作为类方法)
  • 如果您调用obj.extend(Mod),则obj现在具有Mod的方法(作为实例方法),但是的其他实例均未obj.class添加这些方法。
  • extend 是一种公共方法

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

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

但是,模块通常通过猴子修补方法来覆盖 include行为included。这在旧版Rails代码中非常突出。Yehuda Katz提供了更多详细信息

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

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

当然,ruby核心文档始终是解决这些问题的最佳选择。RubySpec项目也是一个很棒的资源,因为他们精确地记录了功能。


22
我知道这是一篇很老的文章,但回复的清晰性使我无法发表评论。非常感谢您的解释。
MohamedSanaulla 2011年

2
@anwar显然,但是现在我可以发表评论了,我设法再次找到了这篇文章。它可在此处获取:aaronlasseigne.com/2012/01/17/explaining-include-and-extend ,我仍然认为该架构使理解变得更加容易
systho

1
此响应的最大优势是如何根据利用率extend将方法应用为类还是实例方法。Klass.extend=类方法,objekt.extend=实例方法。我总是(错误地)认为类方法来自extend和实例来自include
弗兰克·科希尔

16

没错

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

Ruby的默认实现是,如果尚未将该模块的常量,方法和模块变量添加到aModule中,则该模块尚未添加到aModule或其祖先之一。


4

当您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_testextend Module_test并再次运行该代码,我们收到以下错误:undefined method 'func' for #<A:instance_num> (NoMethodError)

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

从上面的代码执行中可以清楚地看到,当我们include一个模块时,其方法成为实例方法,而当我们extend一个模块时,其方法成为类方法


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()功能可让您直接重写include类。Concern中的模块ClassMethods被扩展(复制)到包含类中。


1

我还想解释一下该机制的工作原理。如果我不对,请纠正。

当我们使用时,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(对象的类也是对象)eigenclass添加链接A'

因此,方法查找路径a如下:a => a'=>将模块链接到a'class =>A。

还有一个前置方法可以更改查找路径:

a => a'=>将前置模块添加到A => A =>将包含的模块添加到A

对不起,我的英语不好。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.