Ruby DSL介紹及其在測試數據構造中的使用(1)

什麼是DSL?英文全稱Domain Specific Language,中文解釋爲領域專用語言。顧名思義,DSL是針對某個特定領域而開發的語言。像我們平時接觸 到的C/C++,Java,Python/Ruby,都屬於通用語言,可以爲各個領域編程,通用性有 餘,則針對性不夠強。DSL恰恰是爲了彌補通用語言的這個劣勢而出現的。

       DSL其實並沒有那麼神祕。實際上,在平時的面向對象的 編程中,大家會自覺不自覺的使用DSL的一些方法和技巧。比如,如果我們定義了非常面向 業務的函數,然後這些函數的集合就可以被稱爲一種DSL了。Smalltalk的開發者Blaine Buxton 這樣評價DSL:" I believe a DSL is a healthy bi-product of a good object-oriented design"。

       然而,也正是因爲DSL的構建可以像定義函數一樣簡單,這也讓很多人覺得DSL不過就是定義一些合適的函數——如果那些函數在調用時不用帶上括號的話那就更好 了。誠然,DSL和定義合適的函數之間是有一些類似之處,但是這恰 恰是同一個問題的兩面,DSL更多是從客戶的角度出發看待代碼,定義函數則更多 的從解決問題的方案的角度看待代碼。誠然兩者都有交集,但是出發點卻截然不同。

       我引用Obie Fernandez在QCon上舉的一個例子來看看定義函數和定義DSL的差別。

       場景:如果你想要一杯咖 啡。。。

       你可以定義好一個叫做Latte的類,然後:

 

 如果你不是這個類的設計 者,你會問,第一個參數是什麼意思?第二個呢?第三個?還有,那個false誰能給點提示?

       或者你也可以這樣:

 

 我只 要告訴你,order等號後面,按照你想要的類型去點就可以了。

 

       從上面的例子上,我們大體 可以看出定義函數和DSL之間的風格的差別了。相比較而言,DSL的風格更面向客戶,語法也更加友好。有人會問:“我爲什麼要用DSL?DSL都是給不懂編程的人用的。” 非也。DSL說到底是爲了更加快速的描述問題,解決問題的。像Capistrano(一種網絡應用的部署解決方案)用DSL來定義部署操作:

 

 即便我 們沒有學過Capistrano的語法,從上面的文件中我們也可以大體瞭解這些操 作做了什麼。值得一提的是,上面的文件並不是什麼簡化的說明文檔,而是實實在在可以運行的代碼。

       又比如Rake(Ruby版的Makefile):

 對於熟悉Ruby的人來說,這個DSL基本就是Ruby的語法了,但是可以通過一些關鍵字完成更爲強大的 功能。
       按照Marting Fowler的看法,DSL可以分爲兩種基本類型:內部DSL和外部DSL。顧名思義,外部DSL就相當於實現一種編程語言,也許不如實現一門通用 語言那麼複雜,但是工作量不小;內部DSL就是基於一種通用編程語言的基礎上進行關鍵字的定 義封裝來達到DSL的目的,這種DSL的擴展性可能會受到母語言的影響,對於不熟悉母語 言的人來說可能不是那麼好理解,不過好處就是你可以利用母語言本身通用的其他功能。那麼Ruby DSL自然是一種內部DSL了。
 
 
Ruby DSL介 紹及其在測試數據構造中的使用(2
 
在(1)中 介紹了DSL和 普通的函數定義之間的區別。在(1)的 最後提到,DSL分 爲內部DSL和 外部DSL, 我們再看一遍他們的定義:1. External DSLs 用 不同於host語 言的語言來編寫,通過編譯和解釋器來翻譯成host語 言2. Internal DSLs 將host語 言轉化爲DSL本 身。
       第 二種方式,相比較第一種來說,構建DSL更 爲簡便,還可以利用host語 言本身已有的語言特徵和庫等,缺點是定義DSL的 時候會受到host語 言的限制。但是,如果我們選擇了一門語法友好、靈活的編程語言作爲host語 言的話,那麼我們就可以放大內部DSL的 優點,弱化它的缺點,最終達到效率和回報的最佳值,那麼Ruby是 一個好的選擇。
       Why Ruby?Obie Fernandez 在InfoQ論 壇上列出了Ruby的 一些feature:

當 然了,Ruby的 語法有很多值得玩味的地方,比如。。。(也 許這需要好幾篇文章來寫了)。 好吧,讓我們直接一點:Ruby DSL的 強大直接來自於Ruby強 大的元編程能力。
 
  “元”這個詞來源於希臘語中表示”…之 間, …之後, …超 越’的前綴 ”meta”,有”超越”和”高階”的意思。正如”元數據”表示”數據的數據”, “元模 型”表示”模型的 模型”, “元編程”表 示”編程的編程”。
    在 《programming ruby 1.9》中說過:”Programming is all about building layers of abstractions.” 有些語言,比如C語言,更接近與機器本身,從而導致C語言跟應用之間的距離比較遙遠。而有些語言,比如Ruby,提供了更高層次的抽象,從而讓你在更接近於特定領域的地方開始編程。但 是,當你使用元編程時,你就不再侷限於語言本身的抽象層次了,你可以基於母語言構建新的抽象。於是,” In effect, you’re creating a new, domain-specific programming language—one that’s designed to let you express the concepts you need to solve your particular problem.”
    所 以,從這個角度看,元編程是DSL的基礎。幾乎每一種Ruby DSL的實現都離不開元編程。下面我歸納一下Ruby DSL的幾種方式以及其中利用到的元編程技巧:
 
1.  Class Marcos

你 可以把”ofter_create”, ”has_many”看做一種聲 名。它的實現依賴於元編程中反射功能中的define_method或者instance_eval, 可 以爲函數動態添加方法。
 
 
2.  Methods on an Object

=>

這 是一種構造層次數據的DSL, 完全基於Builder::XmlMarkup的 對象。我們可以發現,生成的XML中定義的關鍵字完全可以由對象的方法名來決定,但是這個對象是如何做到滿足所有任意添加的 關鍵字呢?這其中的實現就依賴於元編程中反射功能中的method_missing方法。
 
3.  自 定義詞彙


在Ruby on Rails中,你可以:

4.years  +  13.days  +  2.hours
或 者我們可以爲這些詞彙定義上下文:

 第 一種你可以叫做”自定義詞彙”, 第二種你可以叫做”單位的實現”, 第三種你可以叫做”上下文相關的詞彙”,但是本質上他們都是一樣的。由於Ruby中 沒有函數,只有方法,意味着所有方法的使用都關聯到一個對象。第一種實現,實際上調用者是Object的 對象,第二種,調用者是4,13,2這些Fixnum的對象,第三種實現通過關鍵字task之 後的關鍵字來調用不同的對象來調用這些方法。
    這 些DSL的實現依賴於Ruby開 放類,block(其實相當於高階函數)等特性。
 
    基 本上Ruby的DSL都是基於以上三種模式的組合和擴展,當然其實現背後的技巧可以非常不同和巧妙。接下來我們 會結合實例來看看這些類型的DSL是如何實現的。
 
原 文鏈接:
http://jdoc.iteye.com/admin/blogs/1267258
http://jdoc.iteye.com/admin/blogs/1269707
 
    (待續。。。)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章