Ruby混入:扩展和包含

16

我一直在阅读关于Ruby混入方法extendinclude的文章,但我还不太确定它们的行为。我理解extend将给定模块的实例方法作为单例方法添加到进行扩展的模块中,而include将基本上将一个模块的内容(方法、常量、变量)附加到进行包含的模块中,在接收者中有效地定义它们。

然而,在尝试了解行为如何体现的过程中,我有一些问题。以下是我的测试设置:

module Baz
  def blorg
    puts 'blorg'
  end
end

module Bar
  include Baz
  def blah
    puts 'blah'
  end
end

module Foo
  extend Bar
end

class Bacon
  extend Bar
end

class Egg
  include Bar
end
所以正如我所预料的那样,模块Bar通过引用方法得到了在Baz(#blorg)中定义的实例方法,并且类Bacon也通过扩展得到了单例方法Bacon::blahBacon::blorg
Bacon.blah  # => blah
Bacon.blorg # => blorg

Egg 获得了在 Bar 中定义的方法(#blah 和现在的 #blorg)作为实例方法。

Egg.new.blah  # => blah
Egg.new.blorg # => blorg

我明白了,这很好。

但是,我不理解使用#ancestors#is_a?方法后得到的响应。

Bacon.ancestors  # => [Bacon, Object, Kernel, BasicObject]
Bacon.is_a? Bar  # => true

Egg.ancestors    # => [Egg, Bar, Baz, Object, Kernel, BasicObject]
Egg.is_a? Bar    # => false
似乎扩展一个模块会导致查询该模块时#is_a?方法返回true,但它并未添加到类的祖先类中;反之,在包含方面,类的祖先类包括被包含的模块,但查询时#is_a?方法却返回false。为什么会这样呢?

这个问题的格式很棒,给你点赞。 - sargas
2个回答

28

区别在于include会将被包含的类添加到包含类的祖先中,而extend会将被扩展的类添加到扩展类的单例类的祖先中。呼~ 首先我们来观察一下会发生什么:

Bacon.ancestors
#=> [Bacon, Object, Kernel, BasicObject]

Bacon.singleton_class.ancestors
#=> [Bar, Baz, Class, Module, Object, Kernel, BasicObject]

Bacon.new.singleton_class.ancestors
#=> [Bacon, Object, Kernel, BasicObject]

Bacon.is_a? Bar
#=> true

Bacon.new.is_a? Bar
#=> false

对于Egg

Egg.ancestors
#=> [Egg, Bar, Baz, Object, Kernel, BasicObject]

Egg.singleton_class.ancestors
#=> [Class, Module, Object, Kernel, BasicObject]

Egg.new.singleton_class.ancestors
#=> [Egg, Bar, Baz, Object, Kernel, BasicObject]

Egg.is_a? Bar
#=> false

Egg.new.is_a? Bar
#=> true

那么foo.is_a? Klass实际上的作用是检查foo.singleton_class.ancestors是否包含Klass。另一个发生的事情是,当创建实例时,类的所有祖先类都成为实例的单例类的祖先类。因此,对于任何新创建的实例,这将计算为真:

Egg.ancestors == Egg.new.singleton_class.ancestors

那么这一切意味着什么呢?extendinclude在不同的层级上执行相同的操作,我希望以下示例可以阐明这一点,因为两种扩展类的方式本质上是等效的:

module A
  def foobar
    puts 'foobar'
  end
end

class B
  extend A
end

class C
  class << self
    include A
  end
end

B.singleton_class.ancestors == C.singleton_class.ancestors
#=> true

class << self 是访问单例类的另一种奇怪语法。因此,extend 实际上只是在单例类中使用 include 的简写形式。


0
Egg.is_a? Egg # => false

include(有效地)更改了Egg类的实例。虽然它不完全相同,但它非常类似于执行以下操作:

class Egg < Bar
end

当扩展将添加类方法时,这非常类似于执行以下操作

class Bacon
  class << self
    include Bar
  end
end

你可以将其视为包含更改类的实例,而扩展实际上是更改类本身。

也许你输错了什么,但是当我执行 Egg.new.is_a? Egg 时,它返回 true。你是不是想说 Egg.is_a? Egg # => false - Daniel Brady

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接