我最近考慮了很多元編程(Metaprogramming)的問題,并希望看到更多這方面技術(shù)的例子和講解。無論好壞,元編程已經(jīng)進(jìn)入Ruby社區(qū),并成為完成各種任務(wù)和簡化代碼的標(biāo)準(zhǔn)方式。既然找不到這類資源,我準(zhǔn)備拋磚引玉寫一些通用Ruby技術(shù)的文章。這些內(nèi)容可能對從其它語言轉(zhuǎn)向Ruby或者還沒有體驗(yàn)到Ruby元編程樂趣的程序員非常有用。
1. 使用單例類 Use the singleton-class
許多操作單個(gè)對象的方法是基于操作其單例類(singleton class),并且這樣可以使元編程更簡單。獲得單例類的經(jīng)典方法是執(zhí)行如下代碼:
2. DSL的使用類方法來修改子類 Write DSL's using class-methods that rewrite subclasses
當(dāng)你想創(chuàng)建一個(gè)DSL來定義類信息時(shí),最常見的問題是怎樣表示信息來讓框架的其它部分使用。以定義一個(gè)ActiveRecord模型對象為例:
在這個(gè)例子中,令人感興趣的是set_table_name的使用。這是怎么起作用的呢?好吧,這里涉及到一個(gè)小魔法。這是一種實(shí)現(xiàn)方法:
這里令人感興趣的是define_attr_method。在這個(gè)例子中我們需要獲得Product類的單例類,但又不想修改ActiveRecord::Base。通過使用單例類我們達(dá)到了這個(gè)目的。我們?yōu)樵瓉淼姆椒ㄈe名,再定義新的存取器(accessor)來返回值。如果ActiveRecord需要table name就可以直接調(diào)用存取器。這種動(dòng)態(tài)創(chuàng)建方法和存取器的技術(shù)在單例類是很常見的,特別是Rails。
3. 動(dòng)態(tài)創(chuàng)建class和module Create classes and modules dynamically
Ruby允許你動(dòng)態(tài)創(chuàng)建和修改class和module。你可以在沒有凍結(jié)的class或module上做任何修改。特定情況下會(huì)很有用。Struct類可能是最好的例子:
這使得可以這樣創(chuàng)建controller:
class View < R '/view/(/d+)'
def get post_id
end
end
4. 使用method_missing來做有趣的事 Use method_missing to do interesting things
除了閉包(block),method_missing可能是Ruby最強(qiáng)大的特性,也是最容易濫用的一個(gè)。用好method_missing的話有些代碼會(huì)變得超級(jí)簡單,甚至是不能缺少。一個(gè)好的例子(Camping)是擴(kuò)展Hash:
Markaby中可以找到另一個(gè)很好的method_missing技巧。以下引用的代碼可以生成任何包含CSS class的XHTML標(biāo)簽:
5. 方法模式的調(diào)度 Dispatch on method-patterns
這對于無法預(yù)測的方法來說可以輕松的達(dá)到可擴(kuò)展性。我最近創(chuàng)建了一個(gè)小型驗(yàn)證框架,核心的驗(yàn)證類會(huì)找出自身所有以check_開頭的方法并調(diào)用,這樣就可以輕松地增加新的驗(yàn)證:只要往類或?qū)嵗刑砑有路椒ā?br /> methods.grep /^check_/ do |m|
self.send m
end
這非常簡單,并且難以置信的強(qiáng)大。可以看一下Test::Unit到處使用這種方法。
6. 替換方法 Replacing methods
有時(shí)候一個(gè)方法的實(shí)現(xiàn)不是你要的,或者只做了一半。標(biāo)準(zhǔn)的面向?qū)ο蠓椒ㄊ抢^承并重載,再調(diào)用父類方法。僅當(dāng)你有對象實(shí)例化的控制權(quán)時(shí)才有用,經(jīng)常不是這種情況,繼承也就沒有價(jià)值。為得到同樣的功能,可以重命名(alias)舊方法,并添加一個(gè)新的方法定義來調(diào)用舊方法,并確保舊方法的前后條件得到保留。
7. 使用nil類來引入空對象的重構(gòu) Use NilClass to implement the Introduce Null Object refactoring
在Fowler的重構(gòu)中,“引入空對象”的重構(gòu)是一個(gè)對象要么存在,要么為空時(shí)有一個(gè)預(yù)定義值。典型例子如下:
8. 學(xué)習(xí)eval的不同版本 Learn the different versions of eval
Ruby有幾種版本的執(zhí)行方法(evaluation)。了解它們的區(qū)別和使用情景是很重要的。有eval、instance_eval、module_eval和class_eval幾種。首先,class_eval是module_eval的別名。其次,eval和其他的有些不同。最重要的是eval只能夠執(zhí)行一個(gè)字符串,其它的可以執(zhí)行block。這意味著eval是你做任何事的最后選擇,它有它的用處,但絕大多數(shù)情況下應(yīng)該用instance_eval和module_eval執(zhí)行block。
eval會(huì)在當(dāng)前環(huán)境執(zhí)行字符串,除非環(huán)境已經(jīng)提供綁定(binding)。(見第11條)
instance_eval會(huì)在接收者(reveiver)的上下文中執(zhí)行字符串或block,沒有指定的話self會(huì)作為接收者。
module_eval會(huì)在調(diào)用的module的上下文中執(zhí)行字符串或block。這個(gè)比較適合在module或單例類中定義新方法。instance_eval和module_eval的主要區(qū)別在于定義的方法會(huì)放在哪里。如果你用String.instance_eval定義foo方法會(huì)得到String.foo,如果是用module_eval會(huì)得到String.new.foo。
module_eval幾乎總是適用;要像對待瘟疫一樣避免使用eval。遵守這些簡單的規(guī)則會(huì)對你有好處。
9. 實(shí)例變量的內(nèi)省 Introspect on instance variables
Rails使用了一個(gè)技巧來使controller中的實(shí)例變量也能用在view中,就是內(nèi)省一個(gè)對象的實(shí)例變量。這會(huì)嚴(yán)重破壞封裝,然而有時(shí)候確實(shí)非常順手。可以很容易的通過instance_variables、instance_variable_get和instance_variable_set實(shí)現(xiàn)。要把所有實(shí)例變量從一個(gè)復(fù)制到另一個(gè),可以這樣:
10. 從block創(chuàng)建Proc并公開 Create Procs from blocks and send them around
把一個(gè)Proc實(shí)例化保存在變量中并公開的做法使得很多API容易使用。這是Markaby用來管理CSS class定義的一種方法。很容易把block轉(zhuǎn)換成Proc:
def create_proc(&p); p; end
create_proc do
puts "hello"
end # => #<Proc ...>
調(diào)用也很容易:
p.call(*args)
如果要用proc來定義方法,應(yīng)該用lambda來創(chuàng)建,就可以用return和break:
p = lambda { puts "hoho"; return 1 }
define_method(:a, &p)
如果有block的話method_missing會(huì)調(diào)用block:
def method_missing(name, *args, &block)
block.call(*args) if block_given?
end
thismethoddoesntexist("abc","cde") do |*args|
p args
end # => ["abc","cde"]
11. 用綁定(binding)來控制eval Use binding to control your evaluations
如果你確實(shí)需要用eval,你可以控制哪些變量是有效的。這時(shí)候要用kernel方法binding來獲得所綁定的對象。例如:
希望這些技巧和技術(shù)已經(jīng)為您闡明了元編程。我并不聲稱自己是Ruby或者元編程方面的專家,這只是我對這個(gè)問題的一些想法。
新聞熱點(diǎn)
疑難解答
圖片精選