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>)
总结:
- 如果需要调用其他对象的方法,必须显式指定接收者
- 私有方法不能明确指定接收者来调用
所以私有方法不能被其他对象调用,不能被自身显式 self
调用,只能隐式 self
调用