標籤:
作為一個動態語言,對象中的方法不會像靜態語言一樣需要驗證確實存在,動態語言的對象之間一直保持著交談,如果你調用一個不曾定義過的方法,程式也不會馬上就報錯而無法運行,只有當運行到你調用這個方法時,解譯器會由於找不到該方法而無法繼續解釋。而在這之前,你可以在啟動並執行過程中添加該方法。你甚至可以用一個方法來處理所有不曾定義過的方法,而做出某些反應。
方法重複
引用書上的一個例子,有一個報價系統,你需要從資料庫中讀取各種儀器裝置的資訊、價格,比如購買一台電腦,需要讀取cpu、滑鼠、鍵盤等資訊。你可能需要一個mouse()方法來讀取滑鼠的型號以及價格,一個cpu()方法來讀取cpu的型號以及價格,一個keyboard()方法來讀取鍵盤型號以及價格等等。這些方法的內容都是類似的,都是從資料庫中執行相應的檢索語句,但是檢索語句卻又不同,這種相似卻不一樣的重複代碼會讓整個程式看起來非常的不cool,從原理上,這些方法執行的都是相同的過程,應該要有一種技術消除這些重複代碼。
在Ruby中,有兩種技術可以完成這項工作:動態方法和幽靈方法。
動態方法動態建立
在Module類中,有一個define_method的方法,Module#define_method方法可以通過傳入的參數和代碼塊來動態地建立方法:
class Computer
? ? def self.define_component(name)
? ? ? ? define_method(name){
? ? ? ? puts "getting #{name} info"
? ? ? ? puts "getting #{name} price"
? ? }
? ? end
end
Computer.define_component :keyboard
obj = Computer.new
obj.keyboard
如上所示,在Computer中並沒有定義keyboard方法,但是可以通過define_method方法,在代碼的其他地方動態地建立keyboard方法,也可以Computer.define_component:cpu,建立一個cpu的方法。?
動態調用
完成了動態建立的工作,接下來就要實現動態調用。Ruby在Object類中,封裝了一個send方法,Object#send方法通過傳入的參數來調用相應的方法。在每一個裝置的方法中,調用的查詢語句是不一樣的,如keyboard應該調用data_source.get_keyboard_info,data_source.get_keyboard_price等方法,而cpu卻需要調用data_source.get_cpu_info,data_source.get_cpu_price方法,通過Object#send方法,就可以實現對這一類方法的調用。如:
obj.send:keyboard
就能調用obj中的keyboard方法,同理可以實現其他方法的動態調用。send同時也可以傳入方法參數:obj.send “method_name”,para。
幽靈方法:method_missing()
method_missing()是Kernel模組的一個方法,而所有的對象都繼承自Kernel,所以所有的對象都有一個method_missing()方法。
method_missing()方法會在找不到方法的時候調用,並返回一個錯誤的資訊,而通過改寫method_missing,可以實現當方法不存在時執行一種統一的操作:
class MyClass
? ? def method_missing(name)
? ? ? ? puts "getting #{name} info"
? ? ? ? puts "getting #{name} price"
? ? end
end
obj = MyClass.new
obj.cpu
但是method_missing()卻不能隨意使用,仔細思考上述代碼,其實存在很多問題,比如會調用你不希望調用的一些方法,如pig.fly等,在上述問題中,如果資料庫中並沒有一些資料,比如,book,如果使用幽靈方法來調用,則會造成錯誤。所以需要加上respond_to?()方法來確認資料庫是否能夠正確響應調用的方法;幽靈方法也會因為不加限制而將一些變數也認為是方法,就需要對方法的範圍做一個限制;同時,在對象的祖先鏈中,可能存在一些方法是你期望用幽靈方法解決的,而事實上,因為解譯器找到了那個方法,儘管不是你想要的那個方法,但是仍然會執行找到的方法,你就需要刪除一些繼承來的方法,或者繼承BasicObject來清除所有繼承來的方法等等。
幽靈方法很cool,但是一定要謹慎使用,保守的我看來是更加喜歡用動態方法的方式去解決了。
Ruby學習之動態調用