ruby中的方法查找
ruby中的方法调用都是 对象.方法 的形式,那么对象如何找到这个方法呢?
首先必须了解祖先链的概念,祖先链就是从一个类开始,到它的父类,再到父类的父类...一直到最终的起点(ruby中是BasicObject类)。这期间经历过的路径就是祖先链。
1混含模块和继承的方法查找
对于一个实例对象,先找它属于的类中是否有对应的实例方法,然后看这个类中是否有模块,如果有,查找模块中是否有对应的方法,如果没有,则查找父类。先看父类的实例方法,再看父类中是否有模块,再看父类的父类..一直到最后,BasicObject类和Kernel模块。
如果还没有,则会去查看method_missing函数,这个函数是内建函数。这个函数默认是报错,当然你也可以重写这个方法,来对没有找到的方法在其中进行处理。
如下:
1 module M 2 def method 3 puts "this is method in module M" 4 end 5 end 6 7 class C 8 include M 9 end 10 11 class D < C;end 12 13 D.new.method 14 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含有多个相同的方法时的方法查找
含有多个相同的方法时,会匹配第一个找到的方法。
如下:
1 module M 2 def method 3 puts "this is method in module M" 4 end 5 end 6 7 module N 8 def method 9 puts "this is method in module N" 10 end 11 end 12 13 class C 14 include M 15 include N 16 end 17 18 C.new.method 19 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中的每个对象实际上都有两个类:多个对象实例共享的类和单例类。对象调用的方法,就是这两个类中的实例方法,以及祖先类和混含模块中的方法。
有单例类的时候,对象的方法查找先查找单例类,然后是单例类混含的模块,然后是对象所属的类,以此类推。
单例类的父类是对象所属的类。
如下:
1 module M 2 def method 3 puts "this is method in module M" 4 end 5 end 6 7 class C 8 end 9 10 c = C.new 11 class << c 12 def method 13 puts "this is method in c' singleton class" 14 end 15 include M 16 p ancestors 17 end 18 19 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
如下:
1 module M 2 end 3 class C 4 include M 5 end 6 c = C.new 7 class << c 8 include M 9 p ancestors 10 end 11 p C.ancestors
输出结果是:
[C, M, Object, Kernel, BasicObject]
[C, M, Object, Kernel, BasicObject]
单例类里混含的模块没有出现在祖先链里,c的单例类和类C的祖先链一样了。
假设类C中包含的不是模块M,而是另一个模块N。
如下:
1 module M 2 end 3 module N 4 end 5 class C 6 include N 7 end 8 c = C.new 9 class << c 10 include M 11 p ancestors 12 end 13 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类方法的单例类
上面讲的是实例对象的单例类。如果是类的单例类呢?(每一个对象都有单例类,类也是对象,当然也有单例类,类方法就是放在单例类里的。)
单例类不能被继承,但是单例类是可以有父类或者子类的。
如下:
1 class C 2 def self.method 3 p "This is method in C" 4 end 5 end 6 class D < C 7 end 8 D.method 9 10 en = class << C;self;end 11 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