参考书籍《Ruby 元编程(第二版)》
Ruby 版本:书上使用的是 2.x,自己使用的 3.1.2
# 动态调用方法
使用 [Object#send] 可以进行动态调用方法。send 方法的第一个参数是要调用的方法名字(可以是字符串或符号),剩下的参数和代码块会直接传递给调用的方法,这样可以在代码运行的最后一刻决定调用那个方法,这个技巧被称为动态派发(Dynamic Dispatch)
class MyClass | |
def my_method(my_arg) | |
my_arg * 2 | |
end | |
end | |
obj = MyClass.new | |
# 常规调用方式 | |
p obj.my_method(3) #=> 6 | |
# 使用 send 方法进行动态调用 | |
p obj.send(:my_method, 3) #=> 6 |
# 动态定义方法
使用 Module#define_method 方法可以随时定义一个方法,只需要提供一个方法名和充当方法主体的块。
class MyClass2 | |
# 动态定义了 my_method 方法 | |
define_method :my_method do |my_arg| | |
my_arg * 3 | |
end | |
end | |
obj2 = MyClass2.new | |
p obj2.my_method(2) #=> 6 |
# 幽灵方法
在 Ruby 中,编译器并不检查方法的调用行为,这意味着可以带调用一个并不存在的方法。
当在对象上调用方法时,如果该方法在祖先链上找不到,那么就会调用 method_missing 方法,它是 BasicObject 的一个私有实例方法,默认行为是抛出 NoMethodError 错误。
我们可以通过重写 method_missing 方法,来拦截那些实际不存在的方法,达到实现幽灵方法(Ghost Method)的效果。
class Lawyer | |
def something; end | |
end | |
nick = Lawyer.new | |
nick.talk_simple # 运行时抛出异常: NoMethodError | |
# 重写 method_missing 方法 | |
class Lawyer2 | |
def method_missing(method, *args) | |
puts "Your call: #{method} (#{args.join(', ')})" | |
puts "(You also passed it a block)" if block_given? | |
end | |
end | |
bob = Lawyer2.new | |
bob.talk_simple('a', 'b') do | |
# a block | |
end | |
#=> 输出结果如下: | |
#=> "Your call: talk_simple (a, b)" | |
#=> "(You also passed it a block)" |
# respond_to? 方法
使用 respond_to? 方法可以询问一个对象是否有对应的方法。
当询问对象是否存在一个幽灵方法时,会再次调用 respond_to_missing? 方法,将其结果作为返回值返回,这个方法是一个钩子方法(Hook Method),默认实现是返回 false,当使用 [method_missing] 处理幽灵方法时,一般就需要对其进行重写。
class Demo | |
def method_missing(name, *args) | |
# 只处理名称为 hello 的幽灵方法 | |
super if name.to_s != 'hello' | |
p "method name: #{name}, args: #{args}" | |
end | |
# 实际存在的一个方法 | |
def test | |
end | |
end | |
a = Demo.new | |
p "respond_to: #{a.respond_to?('test')}" #=> true | |
# 在调用 respond_to? 方法时,如果方法是一个幽灵方法,它会调用 respond_to_missing? 方法确定返回值 | |
p "respond_to: #{a.respond_to?('hello')}" #=> false | |
# 重写 respond_to_missing? 方法之后 | |
class Demo | |
def method_missing(name, *args) | |
# 只处理名称为 hello 的幽灵方法 | |
super if name.to_s != 'hello' | |
p "method name: #{name}, args: #{args}" | |
end | |
# 实际存在的一个方法 | |
def test | |
end | |
# 重写 respond_to_missing? 方法 | |
# 只对名为 hello 的幽灵方法返回 true | |
def respond_to_missing?(method, include_private=false) | |
method == :hello || super | |
end | |
end | |
a = Demo.new | |
p "respond_to: #{a.respond_to?('test')}" #=> true | |
# 当询问的幽灵方法名为 hello 时,返回 true | |
p "respond_to: #{a.respond_to?('hello')}" #=> true | |
# 否则,返回 false | |
p "respond_to: #{a.respond_to?('hello2')}" #=> false |
# 白板类
白板类是对幽灵方法的完善,如果幽灵方法和一个真实方法同名,那么幽灵方法会被忽略。
如果不需要继承来的方法(真实方法),可以通过删除它来解决问题,这种拥有极少数方法的类称为白板类(Blank Slate)。
实现方式一:
- 通过继承 BasicObject 类,因为其只有少数几个实例方法。
- 通过自定义白班类实现,这样可以确保实例方法的名称不会和自己要使用的幽灵方法重名。
实现方式二:
示例代码 class Test
def method_missing(method)
case method
when :display
p 'Display method (Ghost Method)'
end
end
end
t = Test.new
t.display #=> nil
# 上面返回 nil 是因为 Test 类自动继承自 Object 类,而 Object 中已经有一个 display 方法了,它回返回 nil
Object.methods.grep(/^display/) #=> [:display]
1、使用 Module#undef_method 方法:可以删除(包括继承而来的)所有方法。
使用undef_method删除方法 class BlankSlate
def self.hide(name)
# 删除了继承自 Object 的 display 方法
undef_method name if name == :display
end
instance_methods.each { |m| hide(m) }
end
class Test < BlankSlate
def method_missing(method)
case method
when :display
p 'Display method (Ghost Method)'
end
end
end
t = Test.new
t.display #=> "Display method (Ghost Method)"
2、使用 Module#remove_method 方法:只能删除当前类中定义的方法。
使用remove_method删除方法 class Test
def self.hide(name)
# 删除了当前类 Test 中的实例方法 current
remove_method(name) if name == :current
end
def method_missing(method)
case method
when :current
p 'Current method (Ghost Method)'
end
end
def current
p "current method..."
end
instance_methods.each { |m| hide(m) }
end
t = Test.new
t.current #=> "Current method (Ghost Method)"