還記得 Open Classes 可能會造成的問題嗎?當我們利用開放類別的特性,覆寫了類別內原有的方法,就會造成了全域性的改變。這代表改寫方法之後,任何物件呼叫該方法都受到影響。
class String
def length
size > 5 ? "long" : "short"
end
end
"War and Peace".length # => "long"
"War".length # => "short"
不同於 Monkey patch,Ruby 黑魔法 Refinements 所能影響的範圍是區域性,而不是全域性的。
自從 Ruby 2.0之後,你可以使用 refine 以更安全的方式覆寫方法。 用法是先定義一個模組,並且在該模組內裡面放你要覆寫的類別及方法。
當需要 refinements 的時候,並不會自動啟用,你必須以關鍵字 using 才能開始使用。
module StringExtensions
refine String do
def length
size > 5 ? "long" : "short"
end
end
end
using StringExtensions
p "War and Peace".length # => "long"
p "War".length # => "short"
Refinements 與 Monkey patches 類似,但影響範圍不是全域性的。Refinements 的作用域是有限的,且只能活在這兩個地方 :
(1) 自己本身的 refine block itself,
(2) 從你開始呼叫 using 的程式碼為起點,一直到模組結束。那如果你是在最頂層呼叫 using 的話,就是一直到該檔案結束為止。
接著看以下範例:
class MyClass
def my_method
"original my_method()"
end
def another_method
my_method
end
end
module MyClassRefinement
refine MyClass do
def my_method
"refined my_method()"
end
end
end
self # => "main", at the top level
using MyClassRefinement
MyClass.new.my_method # => "refined my_method()"
MyClass.new.another_method # => "original my_method()"
你大概不會很驚訝當你看到 MyClass.new.my_method() 方法的回傳值是 " refined my_method() ",但是為什麼呼叫 another_method() 的回傳值竟然是 "original my_method()" 呢?
明明我們使用 using 是在 another_method() 之前啊!!!原因是在 MyClass 類別中, another_method()方法內呼叫 my_method() 方法是在 using 之前,所以 Ruby 會呼叫原始、未經修改過版本的my_method() 方法。
以上這樣的結果其實是違反一般直覺的反應,因此在使用 Refinement 時,最好重複檢查呼叫方法的順序流程,並且記得 Refinement 只能用在一般模組上,即使類別是繼承自模組,也不能用在類別上。
明天將介紹另外兩個可以取代 Around Aliases 的黑魔法:Refinement Wrapper & Prepended Wrapper