Ruby中方法&常量的查找路径

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.firstnil或者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.firstnil或者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/

你可能感兴趣的:(RUBY)