MetaProgramming Chapter 3
Ruby 元编程 第三章
该文档包含Ruby方法的介绍。
阅读文档,你将学到:
- 动态方法的使用
- 白板类的介绍
动态方法 Dynamic Method
NOTE:
This technique of defining a method at runtime is called a Dynamic Method
在运行时定义方法的技术称为 动态方法
动态调用方法
class A
def a_method(x)
puts x
end
end
a = A.new
a.a_method(1)
a.send(:a_method, 1)
NOTE: Object#send
第一个参数:方法名字(String/Symbol类型)
剩下的参数和代码块会直接传递给调用方法
为什么使用send方法?
send方法替代点标识符(.)调用方法
send方法中将调用的方法名变成参数,这样就可以在代码运行的最后一刻决定调用哪个方法.即:动态派发(Dynamic Dispatch)动态派发
def refresh(options={})
defaults = {}
attributes = [ :input, :output, :commands, :print, :quiet,
:exception_handler, :hooks, :custom_completions,
:prompt, :memory_size, :extra_sticky_locals ]
attributes.each do |attribute|
defaults[attribute] = Pry.send attribute
end
# ...
defaults.merge!(options).each do |key, value|
send("#{key}=", value) if respond_to?("#{key}=")
end
true
end
NOTE: Kernel#respond_to?方法检测诸如Pry#memory_size=这种方法是否存在
若存在则返回true
============QUESTION===============
Kernel#respond_to?
def respond_to?(name)
`!!#{self}['$' + name]`
end
私有性问题
NOTE:
send可以调用任何方法,包括私有方法
如果不用这种破坏私有性的行为,则使用public_send方法
动态定义方法
NOTE:Module#defind_method()方法可以替代def关键字随时定义一个方法, 允许在运行时决定方法名字
重构Computer类
- 添加动态派发:在代码的最后决定调用的方法
- 动态创建方法:在运行时,在类内部定义方法
- 内省方式缩减代码:传入参数,动态调用
class Computer
def initialize(computer_id, data_source)
@id = computer_id
@data_source = data_source
data_source.methods.grep(/^get_(.*)_info$/) { Computer.define_component $1 }
end
def self.define_component(name)
define_method(name) do
# ...
end
end
end
method_missing
NOTE: method_missing:BasicObject的私有实例方法,所有对象都继承自BasicObject类,对所有对象都可用
NOTE: 覆写method_missing方法:截获无主信息,调用实际上不存在的方法
===========QUESTION==============
如何做到找不到调用方法就调用method_missing方法?
查看method_missing 方法
未覆写BasicObject#method_missing:
```
当调用到不存在的方法时,会调用method_missing方法
```ruby
class Lawyer; end
nick = Lawyer.new
nick.talk_simple
< NoMethodError: undefined method `talk_simple' for #<Lawyer:0x007f801aa81938>`
<div class="se-preview-section-delimiter"></div>
幽灵方法
NOTE: 被method_missing方法处理的消息,称为 幽灵方法
动态代理
NOTE: 可以捕获幽灵方法并把它转发给其他对象,称为 动态代理
===========QUESTION==============
Kernel:respond_to_missing? 与respond_to? 的关系
method_missing 的问题
WARNING: 由于调用未定义的方法会导致调用 method_missing 方法, 所以对象可能会接受错误的方法调用
如:写错方法名
class Roulette
def method_missing(name, *args)
person = name.to_s.capitalize
3.times do
number = rand(10) + 1
puts "#{number}..."
end
"#{person} got a #{number}"
end
end
<div class="se-preview-section-delimiter"></div>
number_of = Roulette.new
puts number_of.bob
puts number_of.frank
ERROR:
由于number超出在方法内定义的作用域范围(times..end)被当成Roulette#number 而非变量,
且 未定义方法:Roulette#number,故循环调用Roulette#method_missing,直到调用堆栈溢出
白板类
BasicObject
NOTE: method_missing的另一个问题
NOTE: 白板类: 拥有极少方法的类
NOTE: 如果祖先链上有与重写的幽灵方法命名冲突,幽灵方法将被忽略,故若继承白板类,则不会出现此情况