Dynamic Method
最近因為在應用上覺得了解Ruby的底層可以在程式撰寫的時候有佳的可讀及維護性,一方面也是在網路上看到的資源做個筆記,之前累積太多的技術債該是找時間來還了,這篇就來講講如何讓Ruby做到程式碼自己生產程式碼的一些小tips。
仰賴Ruby的特性,我們可以讓他動態產生method,動態產生是只程式在runtime情況下生成,並不是事先寫好的,而這種應用的情況常常發生於如下的範例情況:
先設定一個SourceData的class,可以從這裡拿到get_name之類的實例方法(instance method)取得實例變數(instance variables)的值。
class SourceData
def initialize(user)
@user = user
end
def get_name
puts "#{@user}_name"
end
def get_address
puts "#{@user}_address"
end
def get_phone
puts "#{@user}_phone"
end
end
我們從另一個叫做User的class,透過SourceData來獲取他的實例變數
class User
def initialize(source_data)
@source_data = source_data
end
def name
@source_data.get_name
end
def address
@source_data.get_address
end
def phone
@source_data.get_phone
end
end
這裡會產生一個問題,如果我們在SourceData的class新增一個get_email
的method,勢必得回去User的class裡面多寫一次email的method,這違反了Open/closed principle,在軟體設計原則有個最常聽到的DRY(Don’t Repeat Yourself),簡單來說不需要一直重複寫相同或是類似的程式碼,除了可以讓code看起來更DRY一些,也在日後維護的時候有更高的可讀性與彈性。
舉例來說如上例的程式碼,實作的內容都相近差不多的,其實我們可以用動態定義的方式來整理這些像是重複的程式碼,Ruby定義動態方法使用define_method
define_mehtod用法
define_method :hello do |param|
puts "Hello, #{param}"
end
這樣一來就可以用define_method來整理剛剛那段看起來有點重複的程式碼
class User
def initialize(source_data)
@source_data = source_data
@source_data.methods.grep(/^get_(.*)$/) {
User.define_infos($1)
}
end
def self.define_infos(name)
define_method(name) {
@source_data.send("get_#{name}")
}
end
end
這樣寫的好處是當程式需要延展跟擴充的時候能夠有足夠的彈性,我們不容易牽一髮動全身,可以專注在焦點本身,以上述例子來說,不論SourceData如何新增get_*
的實例方法時,都能更優雅以動態方式在User class產生method。
Dynamic Dispatch
如果this_object.call_method
,那麼this_object
就稱為接收者,Dynamic Dispatch 是指依照接收者的物件類型,來決定呼叫的方法,這個技巧會非常需要用到 send。
send用法
send
與.
其實一樣,不同的地方是他可以把方法當成參數,傳送給接收者去call_mehtod。
class A
def call_method
puts "This is a method in class A"
end
end
end
我們可以透過send
以及.
來呼叫實例方法A
a = A.new
a.call_method # => "This is a method in class A"
a.send(:call_method) # => "This is a method in class A"
這裡把 call_method 當成 send 的參數(可以是 hash,也可以是 string),然後丟給接收者;既然方法可以被當成物件來丟,意思也就是可以把一些方法存起來成一個 array,然後依照不同需求的時候丟給接收者使用。
這樣一來我們學會使用send
就跟上面所提到的Dynamic Method有異曲同工之妙
因為send可以當作動態定義來呼叫,所以可以試著這樣寫
a = A.new
str = "call_method"
h = :call_method
a.send(str) # => "This is a method in class A"
a.send(h) # => "This is a method in class A"
以上就是動態定義method跟動態call_method的一些小方法。
ref -Ruby 的 Dynamic Dispatch 技巧 -Ruby 也可這樣寫 -http://blog.chh.tw/posts/dynamic-method/