動態方法
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
前者會刪除包括繼承而來的所有方法,而後者只會刪除接收者自己的方法,而保留繼承的方法。
小結
大多數情況下,幽靈方法都不如動態方法來得好,因爲它並不是真正的方法,而只是類似異常的一個功能,使用它會導致諸如難以調試、方法被定義等問題。在能使用動態方法完成需求的場景下,我們都應該優先考慮動態方法而不是幽靈方法