Ruby中包含和扩展有什么区别?

时间:2008-10-01 05:40:58

标签: ruby module include extend

让我了解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!"

6 个答案:

答案 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
  • 如果Mod已包含在Klazz或其祖先之一中,则include语句无效
  • 它还包括在Klazz中的Mod常量,只要它们不冲突
  • 它使Klazz可以访问Mod的模块变量,例如: @@foo@@bar
  • 如果存在循环包含,
  • 会引发ArgumentError
  • 将模块作为调用者的直接祖先附加(即它将Mod添加到Klazz.ancestors,但是Mod没有添加到Klazz.superclass.superclass.superclass的链中。所以,在Klazz#foo中调用super在检查Klazz真正的超类的foo方法之前会检查Mod#foo。有关详细信息,请参阅RubySpec。)。

当然,the ruby core documentation总是最适合这些事情的地方。 The RubySpec project也是一个很棒的资源,因为他们准确地记录了这些功能。

答案 1 :(得分:231)

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

如果您有一个课程Klazz和模块ModMod中包括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

抱歉我的英语不好。