Ruby元編程 第三章 方法

動態方法

class MyClass
  def my_method(my_arg)
    my_arg * 2
  end
end

obj = MyClass.new
obj.send(:my_method, 3) #=> 6

在send方法裏,想調用的方法名變成了參數,這樣就可以在代碼運行的最後一刻決定調用哪個方法。稱之爲動態派發

例如項目中使用的 order_trades_json 方法

同樣由於send方法功能的強大,也帶來了一些弊端,他會破壞封裝性,示例代碼如下

class MyClass
  private def private_method
    puts "private_method"
  end
end
obj = MyClass.new
obj.send(:private_method) #=> private_method

你可以使用send調用任何方法,包括私有方法

如果你不喜歡這種行爲,可以使用public_send

send方法的參數既可以是string也可以使symbol

string和symbol

1.相同的symbol是一個對象,相同的string並不一定是一個對象

"dog".equal?("dog")  #=> false 
:dog.equal?(:dog)    #=> true

2.處理符號相比string,佔用更少的資源 ,性能更優

因爲字符串變量必須具有各種修改其內容的功能,所以字符串的維護和處理的開銷就很大。但是有些時候,我們並不需要修改和處理創建的文本信息,這個時候就應該用符號,減少資源開銷。

動態定義方法
class MyClass
  def self.define_component(name)
    define_method name do
      puts "define method #{name}"
    end
  end
end

obj = MyClass.new
MyClass.define_component :my_method
obj.my_method # => define method my_method

用define_method方法代替def關鍵字定義方法的一個重要原因是
define_method方法允許在運行時決定方法的名字。

define_method方法是類方法,只有類才能調用,也是私有方法
1.也就是說不能有顯式調用,也就是不能有接受者,不能self.define_method這樣調用
2.可以通過send(:define_method)強制調用method_missing方法

method_missing

method_missing方法是BasicObject的一個私有實例方法,當ruby在類中任何地方都找不到某個方法時候,會最終調用它。它會拋出一個NoMethodError進行響應

class MyClass
  def my_method
  end
end

obj = MyClass.new
obj.my_method2 # => undefined method `my_method2' for #<MyClass:0x00007f8c098bbf18> (NoMethodError)

當需要定義很多相似的方法時,可以通過method_missing()方法來方便開發, 使用method_missing方法處理消息,從調用者角度看與普通方法並無差別,但實際上接收者並沒有相應的方法,只是統一進行了處理,這被稱爲幽靈方法.

Mash是一個類似哈希表的對象,它的屬性就像是普通的Ruby的變量。如果想添加一個新的屬性,只要給這個屬性添加一個值即可

iceream = Hashie::Mash.new
iceream.flavor = "strawberry"
iceream.flavor # => "strawberry"
module Hashie
  class Mash < Hashie::Hash
    def method_missing(method_name, *args, &blk)
      return self.[](method_name, &blk) if key?(method_name)
      match = method_name.to_s.match(/(.*?)([?=!]?)/)
      case match[2]
      when "="
        self[mathc[1]] = args.first
        #   ...
      else
        default(method_name, *args, &blk)
      end
    end
    
    #   ...
  end
end

如果方法名字存在,那麼調用 [] 方法返回相應的值,如果方法名以=結尾,那麼method_missing會去掉末尾的=,把餘下的部分作爲屬性,用哈希表相應的鍵值對存儲該屬性。

const_missing

還有一個const_missing方法,作用跟method_missing類似,只是處理的是常量找不到的問題。如Rake中爲兼容而允許在後續版本中使用先前沒有命名空間的老名字,就是這麼實現的

class Module
    def const_missing?(const_name)
        case const_name
        when :Task
            Rake.application.const_warning(const_name)
            Rake::Task
        when :FileTask
            Rake.application.const_warning(const_name)
            Rake:: FileTask
        when :FileCreationTask
            #...
    end
end

白板類

如果不特別指定超類,創建的類默認繼承自Object類
當幽靈方法和真實方法發生名字衝突,幽靈方法就會被忽略
比如Object#display方法,如果你不需要那個繼承來的方法,可以考慮繼承白板類,或者刪除它們
Ruby爲你提供了一個白板類

根類BasicObject只有很少的幾個實例方法

BasicObject.instance_methods # => [:!, :==, :!=, :__send__, :equal?, :instance_eval, :instance_exec, :__id__]
刪除方法

某些情況你可能還要刪除某些方法
這時候可以使用
Module#undef_method
或者Module#remove_method
前者會刪除包括繼承而來的所有方法,而後者只會刪除接收者自己的方法,而保留繼承的方法。

小結

大多數情況下,幽靈方法都不如動態方法來得好,因爲它並不是真正的方法,而只是類似異常的一個功能,使用它會導致諸如難以調試、方法被定義等問題。在能使用動態方法完成需求的場景下,我們都應該優先考慮動態方法而不是幽靈方法

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章