Ruby 是如何调用方法的

前言

当一个方法被调用时,要做的事情其实只有两件,1. 找到它。2. 调用它

接收者 receiver

接收者就是调用方法所在的对象。比如 'str'.to_sym 语句中,'str' 就是接收者。可以形象的理解为向这个接收者 'str' 发送了一条 to_sym 的消息

上面是显式指定接收者的例子,而在 Ruby 中是可以不指定接收者的,像这样:

class MyClass
  def my_method
    test
  end

  def test
    p "I'm Test"
  end
end

MyClass.new.my_method
# I'm Test

当一个字符串被调用时,Ruby 首先会在当前作用域查找是否有这个字符串对应的局部变量,如果没有时就会在 self 这个默认的接收者上调用方法

找到它:祖先链 ancestors

在面向对象的语言中,继承是一个很常见的概念,比如 Python 支持多继承,通过继承多个父类来复用代码,当方法被调用时,首先查找该对象是否有该方法,如果没有,则查找「方法解析顺序」(Method Resolution Order,或 MRO),而 MRO 通过 C3算法(python3)来计算,简单来说就是广度优先

而在 Ruby 中是不支持多继承的,转而使用 mixinx 的方式来实现代码的复用,mro 是通过搜索祖先链一直向上查找。当 Ruby 调用一个方法时,会首先查找对象是否有该方法,如果没有的话,则向上搜索整个祖先链,这就是 one step to the right, then up

class M1;end
class M2 < M1;end

# 查看祖先链
M2.ancestors  # [M2, M1, Object, Kernel, BasicObject]

include

include 是实现 mixinx 最常见的方式,当模块 A 被包含在类(或者模块)B 中时,这个 A 在 B 的祖先链的位置就在 B 之上,例:

module M1;end
module M2;end
module M3
 include M1
 include M2
end

M3.ancestors  # [M3, M2, M1]

prepend

prepend 方法类似于 include,不过这个方法会将模块插入祖先链的下方,例:

module M1;end
module M2;end
module M3
 prepend M1
 prepend M2
end

M3.ancestors  # [M2, M1, M3]

多重包含

当模块 C 包含模块 A、B,模块 B 包含模块 A 时,A 被重复导入,Ruby 会忽略已经被加入祖先链的重复模块导入

module A;end
module B
  include A
end
module C
  prepend A
  include B
end

B.ancestors   # [B, A]
C.ancestors   # [A, C, B]

更复杂的包含:

module M1;end
module M2;end
module M3;end
module M4
  include M1
  include M2
  prepend M3
end

module M5
  prepend M1
  include M2
  include M4
end

M4.ancestors  # [M3, M4, M2, M1]
M5.ancestors  # [M1, M5, M3, M4, M2]

调用它

刚刚我们已经通过祖先链找到了方法,接下来就要执行这个方法。假如有以下方法:

def my_method
  temp = 1
  my_other_method(temp)
end

当执行 my_method 方法时,方法内部需要调用 my_other_method,而该由哪个对象来调用这个方法?

self 关键字

Ruby 中的每一行代码都会在一个对象中被执行–这个对象就是所谓的当前对象。

当前对象可以用 self 关键字来指代,而所有没有明确指明接收者的方法都会在 self 上调用。

class MyClass
  def testing_self
    @var = 10
    my_method()
    self
  end

  def my_method
    @var += 1
  end
end

obj = MyClass.new
obj.testing_self  # <MyClass:0x00007faea4131b90 @var=11>

private 是怎么实现的

private 方法遵从一个简单的规则:

不能明确指定接收者来调用私有方法

class C
  def public_method
    self.private_method
  end

  private def private_method
    1
  end
end

C.new.public_method  # NoMethodError (private method `private_method' called for #<C:0x00007fd0c40feb90>)

总结:

  1. 如果需要调用其他对象的方法,必须显式指定接收者
  2. 私有方法不能明确指定接收者来调用

所以私有方法不能被其他对象调用,不能被自身显式 self 调用,只能隐式 self 调用

参考

Ruby元编程