我最近讀了些文章(比如這篇),宣傳在 Ruby 里使用 method_missing 的。
很多人都與 method_missing 干柴烈火,但在并沒有小心處理彼此之間的關系。所以,我想來探討一下這個問題:
** 我該怎么用 method_missing **
什么時候該抵擋 method_missing 的誘惑
首先,永遠不要在還沒花時間考慮你用得夠不夠好之前,就向 method_missing 的魅力屈服。你知道,在日常生活中,很少會讓你以為的那樣亟需 method_missing:
日常:方法代理
案例:我需要讓這個類能夠使用另一個類的方法
這是我所見過最普遍的使用 method_missing 的情況。這在 gems 與 Rails 插件里頭尤其流行。它的模型類似這樣:
class B
def initialize
@b = A.new
end
def method_missing(method_name, *args, &block)
@b.send(method_name, *args, &block)
end
end
A.new.hi #=> Hi from A
B.new.hi #=> Hi from A
在上例中,情況并不壞,畢竟這里就兩個微不足道的類需要查。但通常,我們是在 Rails 或者其他一些框架的上下文中編程。而你的 Rails 模型繼承自 ActiveRecord,而它又集成自其他一大坨的類,于是現在你就有了一坨高高的堆棧要爬⋯⋯ 在你每次調用 @b.hi 的時候!
你的好基友:define_method
估計現在你在抱怨,“但是史蒂夫,我需要 method_missing” 我告訴你,別忘了其實除了情婦之外,你還有個忠誠的好基友,叫做 define_method。
它允許你動態地定義一個方法(顧名思義)。它的偉大之處在于,在它執行過之后(通常在你的類們加載之后),這些方法就存在你的類中了,簡單直接。在你創建這些方法的時候,也沒有什么繼承鏈需要爬。
define_method 很有愛很可靠,并且能夠滿足你的日常生活。不信我?接著看⋯⋯
“沒問題!” 我賣萌眨眼
你有點難搞哦
容易
class B
A.instance_methods.each do |name|
define_method("what_is_#{name}") do
if @b.respond_to?(name)
@b.send(name)
else
false
end
end
end
end
B.new.what_is_hi #=> "Hi."
B.new.what_is_wtf #=> false
那就沒辦法了,湊合得了。如果你想要代碼更易讀,可以看看我們的ruby delegation library 和 Rails ActiveRecord delegation。
好,我們總結一下,看看 define_method 的真正威力。
修改自 ruby-doc.org 上的 例子
a = B.new
a.barney #=> In Fred
a.wilma #=> Charge it!
a.create_method(:betty) { p self.to_s }
a.betty #=> B
現在你估計在想,總有該用它的時候吧,不然還要它干嘛?沒錯。
動態命名的方法(又名,元方法)
案例:我要依據某種模式提供一組方法。這些方法做的事情顧名思義。我可能從來沒有調用過這些可能的方法,但是等我要用的時候,它們必須可用。
現在才是人話!這其實正是 ActiveRecord 所采用的方式,為你提供那些基于屬性的動態構建的查找方法,比如 find_by_login_and_email(user_login, user_email)。
權衡利弊
當你有一大堆元方法要定義,又不一定用得到的時候,method_missing 是個完美的折衷。
想想 ActiveRecord 中基于屬性的查找方法。要用 define_method 從頭到腳定義這些方法,ActiveRecord 需要檢查每個模型的表中所有的字段,并為每個可能的字段組合方式都定義方法。
假如你的模型有 10 個字段,那就是 10! (362880)個查找方法需要定義。想象一下,在你的 Rails 項目跑起來的時候,有這么多個方法需要一次定義掉,而 ruby 環境還得把它們都放在內存里頭。
老虎?伍茲都做不來的事情。
** 正確的 method_missing 使用方式
(譯者猥瑣地注:要回家了,以下簡要摘譯)
1、先檢查
并不是每次調用都要處理的,你應該先檢查一下這次調用是否符合你需要添加的元方法的模式:
檢查好了,確實要處理的,請記得把函數體包在你的好基友,define_method 里面。如此,下次就不用找情婦了:
自己處理不來的方法,可能父類有辦法,所以 super 一下:
4、昭告天下
** 總結 **
在每個 Ruby 程序員的生活中,這仨方法扮演了重要的角色。define_method 是你的好基友,method_missing 是個如膠似漆但也需相敬如賓的情婦,而 respond_to? 則是你的愛子,如此無虞。
新聞熱點
疑難解答
圖片精選