Metaprogramming 和 Ruby把你的 programming Language 特化成專門對(duì)你要處理的 Domain 的語(yǔ)言( Domain Specifi Language),然後再用這種語(yǔ)言去處理你的問題。 著名的 Lisp 駭客 Paul Graham 在駭客與畫家這本書中說: In Lisp, you don‘t just write your program down toward the language. you also build the language up toward your program. Ruby 也同樣的有這樣的特性。 [編輯]
getter & setter最簡(jiǎn)單的 Metaprogramming in Ruby,就是 attr_reader, attr_writer, attr_accessor。在 Ruby 裡所有的 instance variable 都是必須用 getter 和 setter 來(lái)存取。attr_xxxx 就是用來(lái)生成這些 getter 和 setter 的。 例如:
就等同於
這裡有幾個(gè)要說明的地方
[編輯]
自己寫個(gè) attr_xxxx我們也可以自己寫出類似這樣的 method。比如我們常常有些 instance variable 是一個(gè) flag,在 Ruby 中會(huì)傳回真假值的 method 通常以 ? 做結(jié)尾?,F(xiàn)在希望 Ruby 幫我們自動(dòng)生成這些以 ? 結(jié)尾的 method,就是要能辦到像下面這樣:
attr_flag 的寫法如下:
同樣的功能用 define_method 也可以做到
用 define_method 好像比較簡(jiǎn)潔,但是兩種方法都有人用。 [編輯]
method overload 的例子ruby 本身並沒有提供 method overload 的功能。因?yàn)?Ruby 是一個(gè) dynamic type 的語(yǔ)言,并具有duck typing 所以不需要像 method overload 這樣的功能。不過因?yàn)?Ruby 是非常動(dòng)態(tài)的語(yǔ)言,幫他加上 overload 的功能其實(shí)很簡(jiǎn)單。 先來(lái)看看最我們要達(dá)到什麼效果: class Test # 這是我們要寫的 OverLoad Module include OverLoad # 一般的 method 定義 def t1 puts ‘original t1‘ end # 直接可以用 block 來(lái) overload t1 這個(gè)函數(shù) # 第一個(gè)參數(shù)是要被 overload 的 method 名 # 接下來(lái)是任意個(gè)數(shù)的參數(shù),用來(lái)指用 overload method 的參數(shù)型態(tài)(這裡是沒有指定) # 最後是一個(gè) block,也就是 method 的內(nèi)容 overload :t1 do |a| puts a end # 當(dāng)然可以 overload 很多次 overload :t1 do |a,b| puts a+b end # 這裡就有指定參數(shù)的型態(tài)了 (String, String) overload :t1, String, String do |a,b| puts "the String is #{a} #" end end 下面就是 OverLoad Module: module OverLoad private # 這是將 overload 所接到的「參數(shù)型態(tài)(spec)」轉(zhuǎn)成一個(gè)字串 # 這個(gè)字串在等一下動(dòng)態(tài)產(chǎn)生程式碼的時(shí)候會(huì)用到 def self.spec_to_code(spec) if Integer === spec "args.length == #{spec}" elsif Array === spec i = -1 spec.map {|ts| "#{ts.inspect} === args[#{i+=1}]"}.join(" && ") end end # included 是一個(gè) Hook Method,每當(dāng)有 class include 這個(gè) module 時(shí) # 這個(gè) function 就會(huì)被呼叫 # include 這個(gè) module 的 class 會(huì)被當(dāng)成參數(shù)傳進(jìn)來(lái),也就是這裡的 the_class def self.included(the_class) #這裡幫 include 這個(gè) module 的 class 加上一些新的 method #the_class.class_eval { ooxx} 可以想成把 ooxx 直接寫在 the_class 的內(nèi)容裡 the_class.class_eval do #新加上的函式都是 private 的比較安全 private # 加上 Overload_Dispetch_Db 這個(gè) Hash # 用來(lái)存 method_spec 和真的 method 的對(duì)映 (其實(shí)這裡存的不是 Method,而是 Proc) self.const_set("Overload_Dispetch_Db", Hash.new) # 加上 overload 這個(gè) class method # 以傳入的 [method名, 參數(shù)名字] 做為 key, 將 blok 存入 Hash 之中 def self.overload(method_id, *dispatch_spec, &block) dd = self.const_get("Overload_Dispetch_Db") if dispatch_spec ==[] dd[[method_id,block.arity]] = block else dd[[method_id,dispatch_spec]] = block end # 把原來(lái)的 method 存起來(lái) # 執(zhí)行時(shí)若是在 Overload_Dispetch_Db 裡找不到要求的 method 的話 # 就會(huì)呼叫這個(gè)原來(lái)的這個(gè) method if method_defined?(method_id) && !method_defined?("old_#{method_id}") alias_method "old_#{method_id}", method_id end # 動(dòng)態(tài)的生成 method body = "" # 首先把 Hash 根據(jù)參數(shù)型態(tài)做排序 # 然後一個(gè)一個(gè)的產(chǎn)生程式碼存到 body 之中 # 這裡的排序是把有指定型態(tài)的排在前面 dd.sort do |a,b| if Array === a[0][1] Array === b[0][1] ? (a[0][1].length <=> b[0][1].length) : -1 else Array === b[0][1] ? 1 : a[0][1] <=> b[0][1] end end.each_with_index do |((mid,spec), target), i| body<<<<-EOS #{i == 0 ? ‘if‘ : ‘elsif‘} #{OverLoad.spec_to_code(spec)} Overload_Dispetch_Db[[:#{mid},#{spec.inspect}]].call(*args) EOS end method = <<-EOS def #{method_id.to_s}(*args) #{body} else if respond_to?:old_#{method_id} old_#{method_id}(*args) else raise "Could not dispatch" end end end EOS # 先把原來(lái)的 method undefine # 再把做出來(lái)的 code 加到目前的 class 中 remove_method(method_id) class_eval method end end end end 值得注意的是,在程式中的 Overload_Dispetch_Db 不能直接使用,要用 const_get 和 const_set 來(lái)存取。這是因?yàn)樽償?shù)的 scope 的關(guān)係。在 class.eval 的 block 中,直接使用會(huì)被以為是 module OverLoad 的 Constant 而出現(xiàn)問題。 以之前舉的例子來(lái)看,最後 Test 裡的的 t1 會(huì)是這個(gè)樣子: def t1(*args) if String === args[0] && String === args[1] Overload_Dispetch_Db[[:t1,[String, String]]].call(*args) elsif args.length == 1 Overload_Dispetch_Db[[:t1,1]].call(*args) elsif args.length == 2 Overload_Dispetch_Db[[:t1,2]].call(*args) else if respond_to?:old_t1 old_t1(*args) else raise "Could not dispatch" end end end |
|