Ruby中方法继承、方法查找路径、constant查找路径
@(Ruby)
class A
def method1
end
def self.method2
end
end
class B < A
def method3
end
def self.method4
end
end
b = B.new
b.method1 #nil
b.method3 #nil
B.method2 #nil
可以看到,B不仅继承了A的实例方法,还继承了A的 singleton方法。
module Include
def call(level)
puts "#{level} include"
super(level + 1) rescue nil
end
end
module Prepend
def call(level)
puts "#{level} prepend"
super(level + 1) rescue nil
end
end
module Extend
def call(level)
puts "#{level} extend"
super(level + 1) rescue nil
end
end
class Super
def call(level)
puts "#{level} super"
super(level + 1) rescue nil
end
end
class Klass < Super
include Include
prepend Prepend
def call(level)
puts "#{level} klass"
super(level + 1) rescue nil
end
end
thing = Klass.new
def thing.call(level)
puts "#{level} singleton"
super(level + 1) rescue nil
end
thing.extend(Extend)
thing.call(1)
输出结果为:
1 singleton
2 extend
3 prepend
4 klass
5 include
6 super
此时继承链为
> Klass.ancestors
=> [Prepend, Klass, Include, Super, Object, Kernel, BasicObject]
> thing.singleton_class.ancestors
=> [#>, Extend, Prepend, Klass, Include, Super, Object, Kernel, BasicObject]
可以看到实例 thing 上的方法的寻找路径是根据它的单例类的继承链来决定的。
常量的查找是按照下面的规则进行的:
- 首先查找Module.nesting
路径,这个Module.nesting
跟方法的调用没关系,而是根据方法定义的位置而确定的,
- 否则查找Module.nesting.first.ancestors
- 如果Module.nesting.first
是nil
或者module
,查找Object.ancestors
1、通过Module.nesting
查找,路径为[A]
.
class A
def get_c
C
end
end
class B < A
module C; end
end
B.new.get_c
=> "NameError: uninitialized constant A::C"
可以看到,虽然B
中定义了常量C
,但是get_c
没有从B
中开始查找,而是从自己被定义的模块A
开始查找,找不到而报错。
2、Module.nesting
找不到,则从Module.nesting.first.ancestors
开始查找,此时为会去A
的父类AParent
中查找。
class AParent
C = 'AParent'
end
class A < AParent
def get_c
C
end
end
class B < A
C = 'in B'
end
B.new.get_c
=> "AParent"
3、 如果Module.nesting.first
是nil
或者module
类型,则从Object
查找:
class AParent
C = 'AParent'
end
class A < AParent
module M
def get_c
C
end
end
end
class B
extend A::M
end
B.get_c
=> "NameError: uninitialized constant A::M::C"
因为Object中没有C常量,因此会报错,我们为Object
加上常量C
,此时运行结果如下:
class Object
C = 'In Object'
end
class AParent
C = 'AParent'
end
class A < AParent
module M
def get_c
C
end
end
end
class B
extend A::M
end
B.get_c
=> "In Object"
这是一段Ruby内建的常量查找代码,使用了 binding.eval
,不管在任何地方调用,都能
保证查找的起点都是从它被定义的地方。
class Binding
def const(name)
eval <<-CODE, FILE, LINE + 1
modules = Module.nesting + (Module.nesting.first || Object).ancestors
modules += Object.ancestors if Module.nesting.first.class == Module
found = nil
modules.detect do |mod|
found = mod.const_get(#{name.inspect}, false) rescue nil
end
found or const_missing(#{name.inspect})
CODE
end
end
参考文章:
https://cirw.in/blog/constant-lookup.html
http://pascalbetz.github.io/ruby/2016/03/14/lookup-path/