精緻化¶ ↑
由於 Ruby 的開放類別,您可以重新定義或新增功能至現有類別。這稱為「猴子補丁」。不幸的是,此類變更的範圍是全域性的。所有使用猴子補丁類別的使用者都會看到相同的變更。這可能會造成意外的副作用或程式中斷。
精緻化旨在減少猴子補丁對猴子補丁類別的其他使用者的影響。精緻化提供一種在本地擴充類別的方法。精緻化可以修改類別和模組。
以下是基本的精緻化
class C def foo puts "C#foo" end end module M refine C do def foo puts "C#foo in M" end end end
首先,定義類別 C
。接著使用 Module#refine
為 C
建立精緻化。
Module#refine
建立一個匿名模組,包含類別的變更或精緻化(範例中的 C
)。精緻化區塊中的 self
是這個匿名模組,類似於 Module#module_eval
。
使用下列方式啟用精緻化
using M c = C.new c.foo # prints "C#foo in M"
範圍¶ ↑
您可以在頂層,以及類別和模組中啟用精緻化。您不能在方法範圍中啟用精緻化。精緻化會一直啟用至目前類別或模組定義結束,或是在頂層使用時,至目前檔案結束。
您可以在傳遞給 Kernel#eval
的字串中啟用精緻化。精緻化會一直啟用至 eval 字串結束。
精緻化在範圍中是詞彙的。精緻化只會在呼叫 using
之後的範圍內啟用。using
陳述式之前的任何程式碼都不會啟用精緻化。
當控制權轉移至範圍之外時,精緻化會停用。這表示如果您需要或載入檔案,或呼叫在目前範圍之外定義的方法,精緻化就會停用
class C end module M refine C do def foo puts "C#foo in M" end end end def call_foo(x) x.foo end using M x = C.new x.foo # prints "C#foo in M" call_foo(x) #=> raises NoMethodError
如果在精緻化啟用的範圍中定義方法,當呼叫方法時,精緻化也會啟用。這個範例跨越多個檔案
c.rb
class C end
m.rb
require "c" module M refine C do def foo puts "C#foo in M" end end end
m_user.rb
require "m" using M class MUser def call_foo(x) x.foo end end
main.rb
require "m_user" x = C.new m_user = MUser.new m_user.call_foo(x) # prints "C#foo in M" x.foo #=> raises NoMethodError
由於精緻化 M
在定義 MUser#call_foo
的 m_user.rb
中啟用,因此當 main.rb
呼叫 call_foo
時,也會啟用。
由於 using 是方法,因此精緻化只會在呼叫時啟用。以下是精緻化 M
啟用和未啟用的範例。
在檔案中
# not activated here using M # activated here class Foo # activated here def foo # activated here end # activated here end # activated here
在類別中
# not activated here class Foo # not activated here def foo # not activated here end using M # activated here def bar # activated here end # activated here end # not activated here
請注意,如果稍後重新開啟類別 Foo
,M
中的精緻化不會自動啟用。
在 eval 中
# not activated here eval <<EOF # not activated here using M # activated here EOF # not activated here
未評估時
# not activated here if false using M end # not activated here
在同一個模組中,於多個 refine
區塊中定義多個精緻化時,當呼叫精緻化方法(以下範例中的任何 to_json
方法)時,來自同一個模組的所有精緻化都會啟用
module ToJSON refine Integer do def to_json to_s end end refine Array do def to_json "[" + map { |i| i.to_json }.join(",") + "]" end end refine Hash do def to_json "{" + map { |k, v| k.to_s.dump + ":" + v.to_json }.join(",") + "}" end end end using ToJSON p [{1=>2}, {3=>4}].to_json # prints "[{\"1\":2},{\"3\":4}]"
Method
查詢¶ ↑
當查詢類別 C
的執行個體的方法時,Ruby 會檢查
-
如果
C
啟用了精煉,則依據啟用順序反向執行-
C
精煉中預先附加的模組 -
C
精煉 -
C
精煉中包含的模組
-
-
C
的預先附加模組 -
C
-
C
中包含的模組
如果在任何一點都找不到方法,則會重複執行 C
的超類別。
請注意,子類別中的方法優先於超類別中的精煉。例如,如果在 Numeric
的精煉中定義了方法 /
,則 1 / 2
會呼叫原始的 Integer#/
,因為 Integer
是 Numeric
的子類別,而且會在搜尋超類別 Numeric
的精煉之前進行搜尋。由於方法 /
也存在於子類別 Integer
中,因此方法查詢不會移至超類別。
但是,如果在精煉中於 Numeric
上定義了方法 foo
,則 1.foo
會呼叫該方法,因為 foo
不存在於 Integer
中。
super
¶ ↑
當呼叫 super
時,方法查詢會檢查
-
目前類別中包含的模組。請注意,目前類別可能是精煉。
-
如果目前類別是精煉,則方法查詢會依照上方
Method
查詢區段中的方式進行。 -
如果目前類別有直接超類別,則方法會依照上方
Method
查詢區段中的方式,使用超類別進行。
請注意,在精煉方法中呼叫 super
會呼叫精煉類別中的方法,即使在同一個內容中啟用了另一個精煉。這只適用於精煉方法中的 super
,不適用於包含在精煉中的模組方法中的 super
。
方法內省¶ ↑
在使用內省方法(例如 Kernel#method 或 Kernel#methods)時,不會尊重精煉。
此行為可能會在未來變更。
Refinement
透過 Module#include
繼承¶ ↑
當模組 X 包含在模組 Y 中時,Y 會繼承 X 的精煉。
例如,在下列程式碼中,C 繼承 A 和 B 的精緻化
module A refine X do ... end refine Y do ... end end module B refine Z do ... end end module C include A include B end using C # Refinements in A and B are activated here.
後代的精緻化優先權高於祖先的精緻化。
進一步閱讀¶ ↑
請參閱 github.com/ruby/ruby/wiki/Refinements-Spec 以取得實作精緻化的最新規格。該規格也包含更多詳細資料。