Ruby元编程读书笔记五
Overriding method_missing()
你几乎不大可能亲自调用 method_missing() 方法。不过,你可以覆写它来截获无主的消息。每一个来到 method_missing() 办公桌上的消息都带着调用方法的名字,以及所有调用时传递的参数和块。
class Lawyer
def method_missing(method, *args)
puts "You called: #{method}(#{args.join(', ')})"
puts "(You also passed it a block)" if block_given?
end
end
bob = Lawyer.new
bob.talk_simple('a', 'b') do
# a block
end
#=> You called: talk_simple(a, b)
#=> (You also passed it a block)
Ghost Methods
当需要定义很多相似的方法时,可以通过响应 method_missing 方法来免去一些手工定义这些方法的工作。这点像是在告诉这个对象,“如果别人问你一些不理解的东西,就这样做”。 被 method_missing 方法处理的消息,从调用者的角度看,跟普通方法没什么区别,但是实际上接受者并没有相对应的方法,所以这被称为一个 幽灵方法。
来自 Ruport 的例子
Ruport 是一个 Ruby 报表库。你可以通过调用 Ruport::Data::Table 类来创建表格数据及把它们转换为不同的格式–比如文本格式:
require 'ruport'
table = Ruport::Data::Table.new :column_names => ["country", "wine"],
:data => [["France", "Bordeaux"],
["Italy", "Chianti"],
["France", "Chablis"]]
puts table.to_texts
#=> +--------------------+
#=> | country | wine |
#=> +--------------------+
#=> | France | Bordeaux |
#=> | Italy | Chianti |
#=> | France | Chablis |
#=> +--------------------+
又比如你想仅仅选择法国红酒的数据,并把它们转换为逗号分隔的数据:
found = table.rows_with_country("France")
found.each do |row|
puts row.to_csv
end
#=> France, Bordeaux
#=> France, Chablis
你所做的只是调用了 Ruport::Data::Table 类中一个名为 rows_with_country 的方法,不过这个类的作者怎么能预见到你的数据中会有一个名为 country 的列呢?事实上,该作者对此一无所知。如果深入研究 Ruport 的代码,则会看到 rows_with_country 和 to_csv 都是幽灵方法:
class Table
def method_missing(id, *arg, &block)
return as($1.to_sym, *arg, &block) if id.to_s =~ /^to_(.*)/
return rows_with($1.to_sym => arg[0]) if id.to_s =~ /^rows_with_(.*)/
super
end
# ...
在这段代码中,对 rows_with_country() 的调用被转换为调用一个看起来比较传统的方法—-rows_with(:country),它接受列名作为参数。同样,对 to_csv() 的调用被转换为对 as(:csv) 的调用。如果调用的方法名不是以这两种模式打头,那么 Ruport 会转而调用 Kernel#method_missing 方法,它会抛出一个 NoMethodError 错误,这就是 super 关键字所做的事情。
来自 OpenStruct 的例子
OpenStruct 类来自 Ruby 标准库,一个 OpenStruct 对象的属性用起来就像是 Ruby 的变量,如果想要一个新的属性,那么只需要给它赋个值就行了,然后它就奇迹般地存在了:
require 'ostruct'
icecream = OpenStruct.new
icecream.flavor = "strawberry"
icecream.flavor #=> "strawberry"
这种工作方式背后的原因在于,一个 OpenStruct 对象的属性实际上是幽灵方法。 OpenStruct#method_missing 方法会捕捉对 flavor=() 方法的调用,再砍掉最后那个“=”以获得属性的名字,然后它把属性名和对应的值存放在一个哈希表中。当调用一个不以“=”结尾的方法时,method_missing 方法会在哈希表中查找相应的方法名,并返回对应的值。 OpenStruct 中的代码看起来有点复杂,因为它要处理一些诸如错误处理这样的特殊情况。不过,你可以很容易写一个简化版的开放结构类:
class MyOpenStruct
def initialize
@attributes = {}
end
def method_missing(name, *args)
attribute = name.to_s
if attribute =~ /=$/
@attributes[attribute.chop] = args[0]
else
@attributes[attribute]
end
end
end
icecream = MyOpenStruct.new
icecream.flavor = "vanilla"
icecream.flavor #=> "vanilla"