在Ruby中,include和extend有什么区别?

uyhoqukh  于 2023-10-17  发布在  Ruby
关注(0)|答案(8)|浏览(92)

我只是在学习Ruby元编程。mixin/modules总是让我感到困惑。

*包括:在目标类中混合指定的模块方法作为示例方法
*extend:在目标类中混入指定的模块方法作为类方法

  • 那么,主要的区别仅仅是这个,还是一个更大的龙潜伏?* 例如:
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!"
4smxwvx5

4smxwvx51#

extend-将指定模块的方法和常量添加到目标的元类(即singleton类)。

  • 如果调用Klazz.extend(Mod),现在Klazz有Mod的方法(作为类方法)
  • 如果你调用obj.extend(Mod),现在obj有Mod的方法(作为示例方法),但是obj.class的其他示例没有添加这些方法。
  • extend是一个公共方法
    include-默认情况下,它混合指定模块的方法作为目标模块/类中的示例方法。例如
  • 如果调用class Klazz; include Mod; end;,现在Klazz的所有示例都可以访问Mod的方法(作为示例方法)
  • include是一个私有方法,因为它是在容器类/模块中调用的。
    然而,模块经常通过对included方法进行猴子修补来 * 覆盖 * include的行为。这在遗留的Rails代码中非常突出。more details from Yehuda Katz

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

class Klazz
  include Mod
end
  • 如果Mod已经包含在Klazz中,或者它的一个祖先中,则include语句无效
  • 它还包括Klazz中的Mod常数,只要它们不冲突
  • 它允许Klazz访问Mod的模块变量,例如:@@foo@@bar
  • 如果存在循环包含,则引发ArgumentError
  • 将模块作为调用方的直接祖先(即它将Mod添加到Klazz.祖先中,但Mod不会添加到Klazz.superclass.superclass.superclass.的链中。因此,在Klazz#foo中调用super将在检查Klazz的真实的超类的foo方法之前检查Mod#foo。请参阅RubySpec了解详细信息。

当然,the ruby core documentation始终是这些东西的最佳去处。The RubySpec project也是一个很棒的资源,因为他们精确地记录了功能。

798qvoo8

798qvoo82#

你说的是对的。然而,还有更多的事情。
如果你有一个类Klazz和一个模块Mod,那么在Klazz中包含Mod就可以让Klazz的示例访问Mod的方法。或者,您可以使用Mod扩展Klazz,让 classKlazz访问Mod的方法。但是您也可以使用o.extend Mod扩展任意对象。在这种情况下,单个对象获得Mod的方法,即使所有其他与o具有相同类的对象都没有。

kb5ga3dv

kb5ga3dv3#

是的,没错。
在幕后,include实际上是append_features的别名,它(来自文档):
Ruby的默认实现是将这个模块的常量、方法和模块变量添加到aModule中,如果这个模块还没有被添加到aModule或它的祖先中的话。

u4vypkhs

u4vypkhs4#

当你将一个模块**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一个模块时,它的方法变成了示例方法**,当我们**extend一个模块时,它的方法变成了类方法**。

wz1wpwve

wz1wpwve5#

所有其他的答案都很好,包括挖掘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类中 include 模块ReusableModule,则方法、常量、类、子模块和其他声明将被引用。
如果你用模块ReusableModule * 扩展 * class ClassThatExtends,那么方法和常量会被 * 复制 *。显然,如果不小心,动态复制定义会浪费大量内存。
如果您使用ActiveSupport::Concern,. included()功能允许您直接重写including类。Concern中的模块ClassMethods被 * 扩展 *(复制)到包含类中。

iqih9akk

iqih9akk6#

我也想解释一下它的运作机制。如果我说得不对,请纠正。
当我们使用include时,我们添加了一个从类到包含一些方法的模块的链接。

class A
include MyMOd
end

a = A.new
a.some_method

对象没有方法,只有类和模块有。因此,当a接收到消息some_method时,它开始在a的特征类中搜索方法some_method,然后在A类中搜索,然后在链接到A类的模块中搜索(如果有一些模块的话,按相反的顺序,最后包含的模块获胜)。
当我们使用extend时,我们正在向对象的特征类中的模块添加链接。因此,如果我们使用A.new.extend(MyMod),我们将模块链接到A的示例特征类或a'类。如果我们使用A.extend(MyMod),我们将链接添加到A(对象的,类也是对象)特征类A'
所以a的方法查找路径如下:a => a' =>将模块链接到a' class => A。
还有一个prepend方法可以改变查找路径:
a => a' => prepended modules to A => A => included modules to A
对不起我的英语不好。

ttvkxqim

ttvkxqim7#

我遇到了一个非常有用的article,它比较了includeextendprepend在类中使用的方法
include将模块方法作为示例方法添加到类中,而extend将模块方法作为类方法添加。必须相应地定义要包含或扩展的模块

mtb9vblg

mtb9vblg8#

include提供了一个类,它可以访问模块的方法作为示例

methods
module Plant
  def poisonous?
    true
  end
end

class PlantKingdom
end

class Hemlock < PlantKingdom
  include Plant
end

obj = Hemlock.new()

obj.poisonous?
=> true

Hemlock.poisonous?
=> NoMethodError: undefined method `poisonous?' for Hemlock:Class

extend提供了一个类,可以访问模块的方法作为类方法

module Plant
  def poisonous?
    true
  end
end

class PlantKingdom
end

class Hemlock < PlantKingdom
  extend Plant
end

obj = Hemlock.new()

obj.poisonous?
=> NoMethodError: undefined method `poisonous?' for #<Hemlock:0x00007fa84389c748>

Hemlock.poisonous?
=> true

相关问题