16

我一直在阅读一些关于 Ruby 的 mixin 方法extend和的文章include,但我仍然不太确定这种行为。我知道这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

并且类将(和 now ) 中Egg定义的方法作为实例方法获得。Bar#blah#blorg

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

我明白了,所以这很好。

但是,我不明白使用#ancestorsand#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在查询时返回。为什么会这样?

4

2 回答 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. 发生的另一件事是,当创建实例时,类的所有祖先都成为实例的单例类的祖先。因此,对于任何类的所有新创建的实例,这将评估为 true:

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

whereclass << self只是进入单例类的奇怪语法。所以extend真的只是include单例类的简写。

于 2013-07-09T16:48:12.097 回答
0
Egg.is_a? Egg # => false

包含(有效地)更改Egg类的实例。虽然它不完全相同,但它与做类似的事情非常相似

class Egg < Bar
end

当扩展将添加类方法时,这与做类似的事情非常相似

class Bacon
  class << self
    include Bar
  end
end

您可以将其视为包含更改类的实例,而扩展实际上更改了类。

于 2013-07-09T16:14:21.617 回答