ruby中的方法查找

寻技术 Ruby编程 2023年11月21日 114

  ruby中的方法调用都是 对象.方法 的形式,那么对象如何找到这个方法呢?

  首先必须了解祖先链的概念,祖先链就是从一个类开始,到它的父类,再到父类的父类...一直到最终的起点(ruby中是BasicObject类)。这期间经历过的路径就是祖先链。

  1混含模块和继承的方法查找

  对于一个实例对象,先找它属于的类中是否有对应的实例方法,然后看这个类中是否有模块,如果有,查找模块中是否有对应的方法,如果没有,则查找父类。先看父类的实例方法,再看父类中是否有模块,再看父类的父类..一直到最后,BasicObject类和Kernel模块。

  如果还没有,则会去查看method_missing函数,这个函数是内建函数。这个函数默认是报错,当然你也可以重写这个方法,来对没有找到的方法在其中进行处理。

如下:

 module M
     def method
         puts "this is method in module M"
     end
 end
 
 class C
     include M
 end
 
 class D < C;end
 
 D.new.method
 p D.ancestors

输出是

this is method in module M
[D, C, M, Object, Kernel, BasicObject]

  如这个例子,类D的对象D.new要找method方法:先找D中的实例方法,没有,D中也没有模块。D的父类是C,先找C的实例方法,没有,但C中有模块M,模块M中有method方法,这样就找到了。这个顺序就是按照祖先链的顺序。

  这种查找能到什么程度呢?如祖先链表示的,接下来就是Object类,这是ruby中一切对象的开始。其中有一个模块Kernel。也就是说,如果你在Kernel中定义了一个方法,那么ruby中的所有对象都可以用这个方法。

 

  2含有多个相同的方法时的方法查找

  含有多个相同的方法时,会匹配第一个找到的方法。

如下:

 module M
     def method
         puts "this is method in module M"
     end
 end
 
 module N
     def method
         puts "this is method in module N"
     end
 end
 
 class    C
     include M
     include N
 end
 
 C.new.method
 p C.ancestors

输出结果

this is method in module N
[C, N, M, Object, Kernel, BasicObject]

  类C中包含两个模块M和N,M和N都有method方法。那么调用哪个呢?如果在同一个类中,ruby中新定义的方法会覆盖旧的方法。类似的,模块N相对M是后混如类C的,所以会调用N中的方法。另一个方面,从祖先链来看,N也是排在M的前面,因此也是先调用N的方法。

  祖先链中是模块是怎么排序的呢?祖先链中,一个模块M恰好在包含它的类C中的上一个位置。如这个例子,类C先包含了M,祖先链中是C,M。然后又包含了N,N又恰好在C的上一个位置,于是就变成了C,N,M。

 

  3包含单例类的方法查找

  前面的两种情况是不含单例类的情况,如果含有单例类,就要先考虑单例类了。

  单例类:简单的说就是某个对象特有的类。它只能属于一个对象(即使是同一个类的其他对象实例也不行),因此称为单例类。

  ruby中的每个对象实际上都有两个类:多个对象实例共享的类和单例类。对象调用的方法,就是这两个类中的实例方法,以及祖先类和混含模块中的方法。

  有单例类的时候,对象的方法查找先查找单例类,然后是单例类混含的模块,然后是对象所属的类,以此类推。

  单例类的父类是对象所属的类。

如下:

 module M
     def method
         puts "this is method in module M"
     end
 end
 
 class    C
 end
 
 c = C.new
 class << c
     def method
         puts "this is method in c' singleton class"
     end
     include M
     p ancestors
 end
 
 c.method

输出是

[M, C, Object, Kernel, BasicObject]
this is method in c' singleton class

  单例类,并没有在祖先链中表示出来,但是调用的方法确实是单例类的方法。然后是混含的模块M,然后是父类,以此类推。从祖先链可以看出,单例类的父类是C,是对象c所属的类。

 


   HELP

  在这里出现了一个问题,假如类C中也包含类模块M,那祖先链理论上说应该是M,C,M,Object,Kernel,BasicObject

如下:

 module M
 end
 class    C
     include M
 end
 c = C.new
 class << c
     include M
     p ancestors
 end
 p C.ancestors

输出结果是:

[C, M, Object, Kernel, BasicObject]
[C, M, Object, Kernel, BasicObject]

  单例类里混含的模块没有出现在祖先链里,c的单例类和类C的祖先链一样了。

 

  假设类C中包含的不是模块M,而是另一个模块N。

如下:

 module M
 end
 module N
 end
 class    C
     include N
 end
 c = C.new
 class << c
     include M
     p ancestors
 end
 p C.ancestors

输出结果

[M, C, N, Object, Kernel, BasicObject]
[C, N, Object, Kernel, BasicObject]

  此时,结果和我预期的一样。祖先链中仍然是有c的单例类混含的模块M的。

  这是为什么呢?难道是说,如果单例类里和祖先链上的其他类混含了同样的模块,单例类中的模块名字不显示了?

  另外我也在类Object中包含了M,结果是[C, N, Object, M, Kernel, BasicObject],c的单例类中的模块M也没有。如果是包含N,结果是[M, C, N, Object, N, Kernel, BasicObject],又和预期的一样。难道是单例类和祖先链上的其他类不能包含同样的模块?

  我知道非单例类是可以包含同名的模块的,而且可以同时出现在祖先链里。(我用的是ruby1.9.3)

  路过懂得求解答,不胜感激。


 

  4类方法的单例类

  上面讲的是实例对象的单例类。如果是类的单例类呢?(每一个对象都有单例类,类也是对象,当然也有单例类,类方法就是放在单例类里的。)

      单例类不能被继承,但是单例类是可以有父类或者子类的。

如下:

 class C
     def self.method
         p "This is method in C"
     end
 end
 class D < C
 end
 D.method
 
 en = class << C;self;end
 class E < en;end;

输出结果

"This is method in C"
can't make subclass of singleton class (TypeError)

  结果显示,D.method调用的是C的单例方法,说明D的单例类继承了C的单例类,是它的子类。但是从10-11行,可知,单例类是不能被继承的。

  我觉得可以这么认为:在D继承C的时候,D的单例类继承了C的单例类,所以D可以调用C的类方法。同理,D也可以调用Object类的类方法。

 

  整理一下:

  类的实例对象的方法查找,先找单例类,然后单例类中的模块。再找父类,父类中的模块。以此类推。

  类对象的方法查找,先找单例类(就是类方法),再找父类的单例类,以此类推。

  如果找到多个方法,以找到的第一个方法匹配。

  ruby中,一个类不能被继承,它也可以有子类。例如ruby中类的单例类。

  如果我们用superclass来找父类的话,可得(#代表单例类,假设d是类D的对象,类D继承类C,->表示父类是)

           #d->D->C->Object->BasicObject->nil

       #D->#C->#Object->#BasicObject->Class->Module->Object->BasicObject->nil

 

 

  

关闭

用微信“扫一扫”