Ruby Ruby singleton class ancestors 的问题 (or Bug?)

ibachue · 2013年04月24日 · 最后由 homeway 回复于 2013年11月16日 · 3944 次阅读

Hi all,

一般而言,ancestors 包含三样东西,自身,祖父类,被包含进去的 Module 比方说

RUBY_VERSION # => "2.0.0"
module M; end
class A; include M; end
class B < A; end
A.ancestors #=> [A, M, Object, Kernel, BasicObject]
B.ancestors #=> [B, A, M, Object, Kernel, BasicObject]

但是如果是 Singleton class 的话似乎就不是这样了

module M; end
class A; end
class B < A; end
A.extend M
A.singleton_class.ancestors #=> [M, Class, Module, Object, Kernel, BasicObject]
B.singleton_class.ancestors #=> [M, Class, Module, Object, Kernel, BasicObject]

虽然此时B.singleton_class.superclass会返回#<Class:A>,但是这点并没有在B.singleton_class.ancestors的结果中体现出来,同时它自身#<Class:B>也没有出现在B.singleton_class.ancestors的结果中,这是为什么?是不是 Ruby 的 Bug?谢谢

关于单件类,可以记住一个绕口令:对象的单件类的超类,是这个对象的类;类的单件类的超类,是这个类的超类的单件类。

#1 楼 @baxiaopeng 👍 口诀不错 不过似乎还是没回答我的问题

CRuby 的实现中,ancestors 方法在遍历时,特意跳过了 singleton class.

ancestors 的 receiver 如果是一个 singleton class,那行为就和具体实现相关。如果不是,就把 receiver 放入返回的 Array 中。 也就是说,可以把 singleton class 放入结果中,也可以不放,看具体实现了。通常的 CRuby 是跳过了 singleton class.

单件类就是要特意隐藏起来的。

#5 楼 @skandhas 好吧 但是我还是奇怪为啥不放呢,如果放的话不是和其他部分实现统一起来了吗?

#7 楼 @iBachue 嗯 也许是出于这样的考虑:singleton class 本身和普通的 class 也是有区别的,所以在某些方法上就选择了区别对待 (猜的). Ruby 是规定这个行为是与实现相关的。CRuby 作为 Ruby 的一个实现,跳过了 singleton class 也是符合规定的。至少 CRuby 从 1.8.6 时,在 ancestors 中就跳过了 singleton class. (再早的版本就没看过了)

matz 的设计就是要隐藏单键类,这是一种设计原则。但对于要了解元编程的人来说,又必须要透彻了解隐藏类的全部体系关系。我写了一段测试代码(https://github.com/homeway/fulll_ancestors/blob/master/ancestors.rb


#encoding: utf-8
class Object
  def full_ancestors
    instance_ancestors << "\n" << eigein_ancestors
  end
  def instance_ancestors
    return "我只是一个对象,不能派生实例,也就没有实例的祖先链。" unless respond_to? :ancestors
    return "作为类,若派生实例将产生的祖先链是:[#{ancestors.join(',')}]"
  end
  def eigein_ancestors
    eigeinclass = class << self;self;end
    ancestors = [eigeinclass]
    modules = eigeinclass.included_modules
    x = eigeinclass
    while x.superclass do
      x = x.superclass
      ancestors << x
      deleted = []
      modules.each do |m|
        if x.include? m
          unless x.superclass and x.superclass.include? m
            ancestors << m
            deleted << m
          end
        end
      end
      deleted.each do |m|
        modules.delete m
      end
    end
    "作为对象,我的实际祖先链是:[#{ancestors.join(',')}]"
  end
end

module M; end
class A; end
class B < A; end
A.extend M

puts ''
puts A.full_ancestors
puts B.full_ancestors

那么输出结果大致如下:

作为类,若派生实例将产生的祖先链是:
[A,Object,Kernel,BasicObject]

作为对象,我的实际祖先链是:
[#<Class:A>,#<Class:Object>,#<Class:BasicObject>,Class,Module,Object,Kernel,BasicObject]

作为类,若派生实例将产生的祖先链是:
[B,A,Object,Kernel,BasicObject]

作为对象,我的实际祖先链是:
[#<Class:B>,#<Class:A>,M,#<Class:Object>,#<Class:BasicObject>,Class,Module,Object,Kernel,BasicObject]

Matz 的实现只是过滤掉了#部分的隐藏类,而实际上对象和类在查找方法时,仍将按照上面所输出的实际祖先链顺序来查找。

#9 楼 @homeway 这帖沉了半年又出来了。。

#10 楼 @iBachue 说明这是个值得关注的经典问题嘛!呵呵

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