Ruby ruby 的 extend 方法为什么在类和实例上实现的逻辑不一致

lihaidong · 2014年05月19日 · 最后由 lihaidong 回复于 2014年05月20日 · 3027 次阅读

我们先来看下实例对象 extend module 的情况

module Foo
  def bar
     "bar in the Module Foo"
  end
end

class FooBar
  def bar
     "bar in the class FooBar"
  end
end

f = FooBar.new
f.extend Foo
f.bar 
=> "bar in the Module Foo"

接着看下类对象 extend module 的情况

module Foo
   def bar
       "bar in the Module Foo"
   end
end

class FooBar
  class << self
     def bar
        "bar in the class FooBar"
     end
   end
end

FooBar.extend Foo
FooBar.bar
=> "bar in the class FooBar"

再来看下列代码

module Foo
  def bar
     puts super
     "bar in the Module Foo"
  end
end

class FooBar
  def bar
     "bar in the class FooBar"
  end
end

f = FooBar.new
f.extend Foo
f.bar 

bar in the class FooBar
=> "bar in the Module Foo"

类 extend

module Foo
   def bar
       "bar in the Module Foo"
   end
end

class FooBar
  class << self
     def bar
        puts super
        "bar in the class FooBar"
     end
   end
end

FooBar.extend Foo
FooBar.bar

bar in the Module Foo
=> "bar in the class FooBar"

原来 extend 在实例对象上时,创建了一个虚拟类,本身的那个类作为了虚拟类的超类,对象指针指向了虚拟类,方法的调用优先级自然比超类的高, 然而 extend 用在类对象上时,也创建了一个虚拟类,这个虚拟类就变成了本身这个类的超类。虽然知道它的原理,但不知道它为什么会这样实现,求解?

共收到 18 条回复

我认为一个对象的单例方法优先级最高,上面的第二段代码跟我下面的代码结果一致。

module Foo
  def bar
     "bar in the Module Foo"
  end
end

class FooBar
end

f = FooBar.new

class << f
    def bar
        "bar in class FooBar"
    end
end

f.extend Foo
f.bar

@piecehealth 你这么一说,我突然明白了,ruby 中对于单例的处理是创建一个直接虚拟类,本身的类作为虚拟类的超类,谢谢,开发者果然需要多交流

错的抹掉

@palytoxin 如果按此逻辑,foo(FooBar) 实例对象通过 extend mixin module,那它应该是 (Object(Class) --> Object(eigenclass) --> foo(FooBar) ,但实际上是 (Object(Class) --> foo(FooBar) --> Object(eigenclass)

#4 楼 @lihaidong 没懂,能举个栗子不, foo(FooBar) 是如何插到 Object(Class) 和 Object(eigenclass) 中的?

@palytoxin 帖子中第一段代码

module Foo
  def bar
     "bar in the Module Foo"
  end
end

class FooBar
  def bar
     "bar in the class FooBar"
  end
end

f = FooBar.new
f.extend Foo
p f.bar

f.bar 查找链难道不是 f --> f(eigenclass) ,然后在 f(eigenclass) 发现了 bar? 怎么会跑到了 Object 的 class 和 eigenclass 的中间

8楼 已删除

@palytoxin 那你觉得它内部的类继承结构是什么样的

@palytoxin @lihaidong @piecehealth,第一段代码里,为什么实例对象去 extend 模块的时候,继承树里模块会出现在 singleton class 和自己对象的中间?

f.singleton_class.ancestors #=> [Foo, FooBar, Object, Kernel, BasicObject]

而调用的时候先去 singleton_class 里面找?那肯定就是 singleton_class 位于方法链的最低端,先从这里找了

我是这么想的,singleton_class 肯定位于低端。所以f.extend后父类方法被覆盖了。 第 2 段代码在class << self的时候,就创建了类的 singleton_class。 FooBar.extend的时候,不能覆盖已经创建的 singleton_class 里的同名方法

code show

module A
  def f1
    p 'A f1'
  end
end

a = []
def a.f1
  p 'a f1'
end
a.f1 #=> a f1

a.extend A
a.f1 #=> a f1

看到过一句话:

Object#extend() is simply a shortcut that includes a module in the receiver’s eigenclass.

查找方法顺序是:

  • 先在 eigenclass 中找方法,然后再找类的实例方法

所以先找到了 Foo 中的 bar 方法。

lz 还是先看下 ruby 元编程,把 eigenclass 这个概念弄清楚吧

我又去翻了翻书。 发现之前对 include 和 module 中对方法的继承确实有误解 extend 实际相当于

class << xxx
  include module
end

然后我改写下代码 1

module Foo
  def bar
     "bar in the Module Foo"
  end
end

class FooBar
  def bar
     "bar in the class FooBar"
  end
end

f = FooBar.new
class << f
    include Foo
end
f.bar 

代码 2

module Foo
   def bar
       "bar in the Module Foo"
   end
end

class FooBar
  class << self
     def bar
        "bar in the class FooBar"
     end
   end
end

class << FooBar
    include Foo
end

FooBar.bar

然后代码 2 再多手简化下

module Foo
   def bar
       "bar in the Module Foo"
   end
end

class FooBar
  class << self
     def bar
        "bar in the class FooBar"
     end
     include Foo
   end
end

FooBar.bar

不知道这样 lz 能明白么

然后看一下这些东西的祖先链

module Foo
  def bar
     "bar in the Module Foo"
  end
end

class FooBar
    include Foo
end

class << FooBar
    include Foo
end

FooBar.ancestors #=> [FooBar, Foo, Object, Kernel, BasicObject]

ss = class << FooBar
    self
end
ss.ancestors #=> [Foo, Class, Module, Object, Kernel, BasicObject]

#12 楼 @zealinux 这句话简短到位

#14 楼 @palytoxin 费心了,我也去翻了翻书,又增加不少理解

#16 楼 @lihaidong 没明白就乱说见笑了。这个栗子我还是有点自己的理解 其实上方的代码最重要的区别在class << self这句

class << self
   def bar
      "bar in the class FooBar"
   end
 end

这个打开类,是在 eigenclass 内,定义了一个 instance_method bar

FooBar.singleton_class.instance_methods(false)
#=> [:bar]

FooBar.extend Foo 把 Foo 混在了 eigenclass 上方 extend 混入,实际上是在 eigenclass 里调用了 include,那么这个模块以虚拟类放在了 eigenclass 的上方

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

一个定义在 eigenclass 内,一个被混入放在了 eigenclass 上 这就是为什么" extend 方法为什么在类和实例上实现的逻辑不一致" 其实是一致的,只不过你用的这个栗子 class<<self,正好造成了这种困扰

昨天扣了半天书本,觉得把自己说服了,欢迎讨论,求拍砖: )

#17 楼 @palytoxin 你说的挺到位的,我重新梳理下,算是告一段落吧

  1. @zealinux 提及的 extend 就是在对象的 eigenclass 里 include module,所以
FooBar.extend Foo => class << FooBar ; include Foo; end
f.extend Foo =>  class << f; include Foo; end

2.eigenclass 里定义的方法优先级最高,因为 ruby 内部把它作为当前对象类的直接子类,所以 FooBar.bar 方法查找的第一个类是 FooBar 对象的 eigenclass,在代码一中直接在类定义中通过

class <<  self
   def bar 
     "bar in the class FooBar"
   end
end

定义了 eigenclass 的 bar 方法,故调用了此方法,mixin 的 module 作为了 proxy 类。 f.bar 方法链的一个搜索类也是对象 f 的 eigenclass,在此 eigenclass 内没有找到,在 f 的 eigenclass mixin 的 Foo 模块中找到,故 f.bar 调用的是模块的方法

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册