Ruby元编程(三)
参考书籍《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方法:可以删除(包括继承而来的)所有方法。
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方法:只能删除当前类中定义的方法。
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)"