這是 Metaprogramming Ruby 2 的閱讀筆記,只會記錄我覺得重要的地方。如果你想要了解完整的內容或是想讓Ruby程式做一些神奇的事,強烈推薦去讀讀這本書。
Dynamic Dispatch:我們可以使用send來呼叫method。
class A
def say_hi
"Hi"
end
def say_hello
"Hello"
end
end
a = A.new
p a.say_hi # Hi
p a.say_hello # Hello
['hi', 'hello'].each do |what|
p a.send("say_#{what}")
end
使用send呼叫method的好處在於可以延遲「決定呼叫哪個method」的時間點,因為method的名稱變成了send參數,我們可以在使用send時才決定要呼叫哪個method。
書上舉了pry使用dynamic dispatch的例子。
send可以用來呼叫private method,不過能大越大,責任越重,你可以呼叫不代表你能呼叫。
class A
private
def think
"Hmmmm..."
end
end
a = A.new
p a.think # private method `think' called for #<A:0x007fdd20982588> (NoMethodError)
p a.send(:think) # Hmmmm...
Dynamic Method:使用define_method動態建立method。
class A
['hi', 'hello'].each do |what|
define_method "say_#{what}" do |is_loud|
"#{what.capitalize}#{is_loud ? "~~~" : ""}"
end
end
end
a = A.new
p a.say_hi(false) # Hi
p a.say_hello(true) # Hello~~~
當method lookup沒找到呼叫的method,就會去呼叫method_missing
這個private method,這個method是在BasicObject中,所以所有的物件都有這個method。
我們可以override method_missing,來處理找不到method時要做的動作。
Ghost Method:覆寫method_missing來處理method的呼叫,從外部來看與一般的method呼叫沒有什麼不同,但內部實際上卻是找不到method而使用method_missing來處理。
class A
def method_missing(method, *arg)
if method =~ /say_(.*)/
$1.capitalize
else
super
end
end
end
a = A.new
p a.say_hi
p a.say_hello
p a.walk # undefined method `walk' for #<A:0x007f85b9112a10> (NoMethodError)
Dynamic Proxy:Ghost method很常用來串接第三方服務,因為當API變動時,ghost method會自動生成對應的method。
使用ghost method建立的method,對respond_to?
是沒有反應的。
class A
def method_missing(method, *arg)
if method =~ /say_(.*)/
$1.capitalize
else
super
end
end
end
a = A.new
p a.say_hi # Hi
p a.respond_to?(:say_hi) # false
respond_to?
會去呼叫respond_to_missing?
,而respond_to_missing?
預設就是用來處理ghost method,理論上如果你建立了ghost method,要記得一並覆寫respond_to_missing?
,讓它對ghost method可以回傳true。
class A
def method_missing(method, *arg)
if method =~ /say_(.*)/
$1.capitalize
else
super
end
end
def respond_to_missing?(method, include_private = false)
if method =~ /say_(.*)/
return true
else
super
end
end
end
a = A.new
p a.say_hi # Hi
p a.respond_to?(:say_hi) # true
Module#const_missing的功能就像method_missing,只不過它是用來偵測給的const有沒有存在。書上舉了一個Rake的例子。
書上舉了一個例子說明濫用ghost method造成的問題,因為method_missing會接所有沒找到method的狀況,在使用method_missing時一定要限制使用的條件,而不是所有的情況都採用覆寫的定義。
Ghost method有一個缺點就是如果有一個實際存在相同名稱的method,則實體的method會優先使用,因為method_missing是在找不到method的情況下才會去呼叫。修正這個問題的方法有兩種,一是移除實體的method,二是使用blank slates(空白石板)。
Blank Slate:一個包含最少method的object,在ruby裡,就是BasicObject。
直接繼承BasicObject就可以減少經有繼承體係獲得的method。不過因為respond_to?這個method是來自Object,BasicObject並沒有這個method,所以這意味如果使用BasicObject,就沒有respond_to?可以使用,剛才的respond_to_missing?的修改也可以刪掉了。
有兩種方式可以移除method:
class A
def say_hi
"A say hi"
end
end
class B < A
def say_hi
"B say hi"
end
end
b = B.new
p b.say_hi # B say hi
class B
remove_method(:say_hi)
end
p b.say_hi # A say hi
class B
undef_method(:say_hi)
end
p b.say_hi # undefined method `say_hi' for #<B:0x007fae5c845ff0> (NoMethodError)
書上舉了XML builder的例子,把除了reserved method(雙底線開頭的method)與instance_eval保留之外,其它的method都被undef了:
class BlankSlate
def self.hide(name)
# ...
if instance_methods.include?(name._blankslate_as_name) && name !~ /^(__|instance_eval$)/
undef_method name
end
end
# ...
instance_methods.each { |m| hide(m) }
end
Object#methods
之中。相較起來dynamic method的行為就比較像是一般的method定義的方式,只是它可以在程式執行階段被動態的定義。