Ruby在最近得以流行的主要原因是它非常適合用來編寫內部領域特定語言(Internal DSL)。內部領域特定語言是指在另一種語言(宿主語言)之上編寫的領域特定語言。目前,用Ruby編寫DSL更有日趨火爆的跡象。
內部領域特定語言是一個在Lisp圈子里一直非常流行的想法。很多Lisp語言擁護者炮轟Ruby在這方面沒有帶來任何新意。但令Ruby與眾不同的一點是:它提供了多種技術來開發內部DSL。雖然Lisp也提供了一些很好的機制,但相對于Ruby,它的選擇還是較少的。
本章通過一個例子來探討這些技術,以使你對這些技術有所體會,并讓你能在它們之間進行比較取舍。
3.1 巢穴
接下來,我將用一個簡單的例子來探討這些技術。這個例子是一個常見但很有趣的抽象配置問題。在各種各樣的設備中,我們都會遇到這樣的問題:如果要有x,那么必須要有一個相匹配的y。當我們購買電腦、安裝軟件或做其他一些更加有趣的事情時,都會遇到這個問題。
在這個例子中,讓我們想象有這么一家公司,它專門將復雜的設備提供給那些腦子里整天想著要征服世界的狂徒。從此類電影的數量來看,這是一個很大的市場。而這些狂徒藏身的巢穴,被一些極具魅力秘密特工不斷搗毀,更增加了對這些設備的持續需求。
本章將用DSL來描述這些狂徒們放置在巢穴中設備的配置規則。此例中的DSL將會描述兩類事物:物件(item)和資源(resource)。物件表示一些具體的事物,比如攝像機(camera)和酸性溶液(acid bath)等;而資源表示一些大量的原材料,比如電(electricity)等。
此例涉及兩種資源:電和酸(acid)。假設每種資源都擁有多種不同的屬性。比如,所有物件的電力都由巢穴中的電廠供應(這些狂徒們可不想使用社會公共服務)。所以在這個抽象表示中,每件資源都需要有它自己獨立的類。
為了更好地描述這個問題,讓我們假設只有兩種類別的資源:簡單的和復雜的。簡單資源(比如電)只有為數不多且數量固定的屬性,所以可以在生成函數的參數中傳入這些屬性。而復雜資源(比如酸)有很多可選的屬性,它們需要一些單獨的設置方法來設置屬性。雖然此例中的酸實際上只有兩種屬性,但不妨讓我們把它想象成擁有幾十種不同屬性的復雜資源。
而關于物件,有三個特點需要聲明:它們使用資源,它們提供資源,并且它們依賴于巢穴中的其他物件。
好了,不繼續吊你的胃口了,讓我們馬上來看看這個抽象表示的實現吧。注意,在所有將要討論的例子中,都將使用同一個抽象表示。
下載 lairs/model.rb
class Item
attr_reader :id, :uses, :provisions, :dependencies
def initialize id
@id = id
@uses = []
@provisions = []
@dependencies = []
end
def add_usage anItem
@uses << anItem
end
def add_provision anItem
@provisions << anItem
end
def add_dependency anItem
@dependencies << anItem
end
end
class Acid
attr_accessor :type, :grade
end
class Electricity
def initialize power
@power = power
end
attr_reader :power
end
把所有的特定配置放在一個配置對象中。
下載 lairs/model.rb
class Configuration
def initialize
@items = {}
end
def add_item arg
@items[arg.id] = arg
end
def [] arg
return @items[arg]
end
def items
@items.values
end
end
為了描述清楚本章所要闡述的問題,我們只需定義少量物件以及它們的規則:
一種使用12單位電和五級鹽酸的酸性溶液;
一個使用1單位電的攝像機;
一個供應11單位電并且依賴于一個安全排氣口(secure air vent)的小型電廠(small power plant)。
在抽象表示中可以這樣描述這些物件的規則。
下載 lairs/rules0.rb
config = Configuration.new
config.add_item(Item.new(:secure_air_vent))
config.add_item(Item.new(:acid_bath))
config[:acid_bath].add_usage(Electricity.new(12))
acid = Acid.new
config[:acid_bath].add_usage(acid)
acid.type = :hcl
acid.grade = 5
config.add_item(Item.new(:camera))
config[:camera].add_usage(Electricity.new(1))
config.add_item(Item.new(:small_power_plant))
config[:small_power_plant].add_provision(Electricity.new(11))
config[:small_power_plant].add_dependency(config[:secure_air_vent])
雖然能形成可用的配置但這樣的代碼并不流暢。下面,我們將嘗試用不同的方法來編寫代碼,以更好地描述這些規則。
3.2 使用全局函數
函數是程序最基本的結構,它是生成軟件和為程序引入領域名稱的最簡單方式。
所以,我們編寫DSL的初次嘗試就是調用一系列的全局函數。
下載 lairs/rules8.rb
item(:secure_air_vent)
item(:acid_bath)
uses(acid)
acid_type(:hcl)
acid_grade(5)
uses(electricity(12))
item(:camera)
uses(electricity(1))
item(:small_power_plant)
原文轉自:http://www.ituring.com.cn/article/17818