程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> 更多編程語言 >> 更多關於編程 >> Ruby元編程之夢中情人method_missing方法詳解

Ruby元編程之夢中情人method_missing方法詳解

編輯:更多關於編程

       這篇文章主要介紹了Ruby元編程之夢中情人method_missing方法詳解,本文講解了我該怎麼用 method_missing 、方法代理、define_method、什麼時候用 method_missing、元方法等內容,需要的朋友可以參考下

      我最近讀了些文章(比如這篇),宣傳在 Ruby 裡使用 method_missing 的。

      很多人都與 method_missing 干柴烈火,但在並沒有小心處理彼此之間的關系。所以,我想來探討一下這個問題:

      ** 我該怎麼用 method_missing **

      什麼時候該抵擋 method_missing 的誘惑

      首先,永遠不要在還沒花時間考慮你用得夠不夠好之前,就向 method_missing 的魅力屈服。你知道,在日常生活中,很少會讓你以為的那樣亟需 method_missing:

      日常:方法代理

      案例:我需要讓這個類能夠使用另一個類的方法

      這是我所見過最普遍的使用 method_missing 的情況。這在 gems 與 Rails 插件裡頭尤其流行。它的模型類似這樣:

      代碼如下:

      class A

      def hi

      puts "Hi from #{self.class}"

      end

      end

      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

      如此,B 就擁有了 A 的所有實例方法。但是讓我們想想,在調用 @b.hi 的時候都發生了什麼。你的 ruby 環境沿著繼承鏈一路找 hi 這個方法,到最後,恰恰在丟出個 NoMethodError 前,它調了 method_missing 這個方法。

      在上例中,情況並不壞,畢竟這裡就兩個微不足道的類需要查。但通常,我們是在 Rails 或者其他一些框架的上下文中編程。而你的 Rails 模型繼承自 ActiveRecord,而它又集成自其他一大坨的類,於是現在你就有了一坨高高的堆棧要爬…… 在你每次調用 @b.hi 的時候!

      你的好基友:define_method

      估計現在你在抱怨,“但是史蒂夫,我需要 method_missing” 我告訴你,別忘了其實除了情婦之外,你還有個忠誠的好基友,叫做 define_method。

      它允許你動態地定義一個方法(顧名思義)。它的偉大之處在於,在它執行過之後(通常在你的類們加載之後),這些方法就存在你的類中了,簡單直接。在你創建這些方法的時候,也沒有什麼繼承鏈需要爬。

      define_method 很有愛很可靠,並且能夠滿足你的日常生活。不信我?接著看……

      代碼如下:

      class B

      define_method(:hi) do

      @b.hi

      end

      end

      “可是我有一大坨方法要定義!” 你抱怨

      “沒問題!” 我賣萌眨眼

      代碼如下:

      class B

      [:hi, :bye, :achoo, :gesundheit].each do |name|

      define_method(name) do

      @b.send(name)

      end

      end

      end

      可是我懶得把它們一個個寫出來!

      你有點難搞哦

       代碼如下:

      class A

      # ... lots of methods in here

      end

      class B

      A.instance_methods.each do |name|

      define_method(name) do

      @b.send(name)

      end

      end

      end

      那假如我要定義的方法跟原本的有那麼一些些不一樣呢?

      容易

       代碼如下:

      class A

      def hi

      puts "Hi."

      end

      end

      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 上的 例子

       代碼如下:

      class A

      def fred

      puts "In Fred"

      end

      def create_method(name, &block)

      self.class.send(:define_method, name, &block)

      end

      define_method(:wilma) { puts "Charge it!" }

      end

      class B < A

      define_method(:barney, instance_method(:fred))

      end

      a = B.new

      a.barney #=> In Fred

      a.wilma #=> Charge it!

      a.create_method(:betty) { p self.to_s }

      a.betty #=> B

      什麼時候用 method_missing?

      現在你估計在想,總有該用它的時候吧,不然還要它干嘛?沒錯。

      動態命名的方法(又名,元方法)

      案例:我要依據某種模式提供一組方法。這些方法做的事情顧名思義。我可能從來沒有調用過這些可能的方法,但是等我要用的時候,它們必須可用。

      現在才是人話!這其實正是 ActiveRecord 所采用的方式,為你提供那些基於屬性的動態構建的查找方法,比如 find_by_login_and_email(user_login, user_email)。

      代碼如下:

      def method_missing(method_id, *arguments, &block)

      if match = DynamicFinderMatch.match(method_id)

      attribute_names = match.attribute_names

      super unless all_attributes_exists?(attribute_names)

      if match.finder?

      # ...you get the point

      end # my OCD makes me unable to omit this

      # ...

      else

      super # this is important, I'll tell you why in a second

      end

      end

      權衡利弊

      當你有一大堆元方法要定義,又不一定用得到的時候,method_missing 是個完美的折衷。

      想想 ActiveRecord 中基於屬性的查找方法。要用 define_method 從頭到腳定義這些方法,ActiveRecord 需要檢查每個模型的表中所有的字段,並為每個可能的字段組合方式都定義方法。

      代碼如下:

      find_by_email

      find_by_login

      find_by_name

      find_by_id

      find_by_email_and_login

      find_by_email_and_login_and_name

      find_by_email_and_name

      # ...

      假如你的模型有 10 個字段,那就是 10! (362880)個查找方法需要定義。想象一下,在你的 Rails 項目跑起來的時候,有這麼多個方法需要一次定義掉,而 ruby 環境還得把它們都放在內存裡頭。

      老虎·伍茲都做不來的事情。

      ** 正確的 method_missing 使用方式

      (譯者猥瑣地注:要回家了,以下簡要摘譯)

      1、先檢查

      並不是每次調用都要處理的,你應該先檢查一下這次調用是否符合你需要添加的元方法的模式:

       代碼如下:

      def method_missing(method_id, *arguments, &block)

      if method_id.to_s =~ /^what_is_[w]+/

      # do your thing

      end

      end

      2、包起來

      檢查好了,確實要處理的,請記得把函數體包在你的好基友,define_method 裡面。如此,下次就不用找情婦了:

      代碼如下:

      def method_missing(method_id, *arguments, &block)

      if method_id.to_s =~ /^what_is_[w]+/

      self.class.send :define_method, method_id do

      # do your thing

      end

      self.send(method_id)

      end

      end

      3、擦屁股

      自己處理不來的方法,可能父類有辦法,所以 super 一下:

      代碼如下:

      def method_missing(method_id, *arguments, &block)

      if method_id.to_s =~ /^what_is_[w]+/

      self.class.send :define_method, method_id do

      # do your thing

      end

      self.send(method_id)

      else

      super

      end

      end

      4、昭告天下

      代碼如下:

      def respond_to?(method_id, include_private = false)

      if method_id.to_s =~ /^what_is_[w]+/

      true

      else

      super

      end

      end

      要告訴別人,你的類雖然暫時還沒有這個方法,但是其實是能夠響應這方法的。

      ** 總結 **

      在每個 Ruby 程序員的生活中,這仨方法扮演了重要的角色。define_method 是你的好基友,method_missing 是個如膠似漆但也需相敬如賓的情婦,而 respond_to? 則是你的愛子,如此無虞。

    1. 上一頁:
    2. 下一頁:
    Copyright © 程式師世界 All Rights Reserved