provides(electricity(11))
depends(:secure_air_vent)
這些函數名組成了DSL的詞匯表:item聲明了一個物件,uses表明一個物件使用了一種資源。
這段DSL描述的配置規則其實就是在建立各個對象之間的關系。當描述一個攝像機使用1單位電時,就是在建立這個物件和電這種資源之間的關聯。在第一個巢穴表示中,關聯關系是由命令的順序決定的。uses(electricity(1))這個命令緊跟著攝像機的聲明,所以它被應用于攝像機這個物件。我們可以說,關聯關系是由語句的順序上下文隱式定義的。
計算機和人不同,我們可以通過閱讀DSL文本而獲知命令的順序,但計算機需要一些額外的幫助才能理解它們。當計算機導入DSL時,可以用一些特殊的變量來跟蹤上下文關系,這些變量被稱作上下文變量。用一個上下文變量來跟蹤當前使用物件的代碼如下。
下載 lairs/builder8.rb
def item name
$current_item = Item.new(name)
$config.add_item $current_item
end
def uses resource
$current_item.add_usage(resource)
end
因為使用了全局函數,所以需要使用全局變量來作為上下文變量。這么做確實不十分恰當,但我們馬上會看到在很多語言中都可以避免全局函數的使用。事實上,使用全局函數只是權宜之計。
我們也可以采用同樣的方法來處理酸(acid)的屬性。
下載 lairs/builder8.rb
def acid
$current_acid = Acid.new
end
def acid_type type
$current_acid.type = type
end
用順序關系來描述物件和資源之間的關聯還湊合能用,但用它來描述具有依賴關系的物件之間的關聯時就顯得不太合適。這時候需要在它們之間建立一些顯式的關聯。比如,可以在聲明一個物件時(item(:secure_air_vent))賦給它一個標識符,在以后需要使用它時用那個標識符引用物件(depends(:secure_air_vent))。當然,上面描述的小型電廠依賴于安全排氣口的關聯是通過順序關系建立的。
物件和資源的不同之處在于,資源就是Evans所說的值對象(value object)[Eva03],它們只被物件關聯。而物件可以在DSL中通過依賴關系被任意關聯。所以,物件需要一些標識符以備在將來引用。
Ruby用symbol來處理這類標識符::secure_air_vent。symbol是以冒號開頭的、不含空格的字符串。很多主流語言都沒有這種數據類型。在這種特殊的用途中,可以把它們當作字符串一樣看待。但symbol并不具備很多字符串操作,而且所有等值的symbol均共享一個實例,這使得對symbol的搜索更加高效。但在此例中使用它們的主要原因,是symbol所表達的含義正好跟我在這里使用它們的意圖不謀而合,即把它們當作符號使用。我把:secure_aire_vent作為一個符號,而不是一個字符串??梢钥吹?,選擇一種正確的數據類型可以幫助我們更加清晰地表達我們的意圖。
另一種方式當然是使用變量。但在DSL中我不太喜歡使用變量。變量的問題就在于,它們是可變的。它們可以被賦予不同對象,因此我們不得不跟蹤變量中的對象到底是什么。變量是一種有用的工具,但對它們的跟蹤非常棘手。在DSL中我們通常應該避免使用它們。而標識符始終指向同一個對象,不會改變。
對于物件依賴關系的表述,標識符是必須的,我們同樣可以用它代替順序關系來描述資源。
下載 lairs/rules7.rb
item(:secure_air_vent)
item(:acid_bath)
uses(:acid_bath, acid(:acid_bath_acid))
acid_type(:acid_bath_acid, :hcl)
acid_grade(:acid_bath_acid, 5)
uses(:acid_bath, electricity(12))
item(:camera)
uses(:camera, electricity(1))
item(:small_power_plant)
provides(:small_power_plant, electricity(11))
depends(:small_power_plant, :secure_air_vent)
標識符的使用意味著關聯關系的顯式定義,而且意味著全局上下文變量的多余。這是一舉兩得的事情:我喜歡清晰地定義關系,而且我也討厭使用全局變量。但這樣做的代價是DSL看起來會更加冗長,值得用一些隱式機制使DSL變得更加清晰易讀。
3.3 使用對象
如前所述那樣使用函數的一個主要問題是:需要使用全局函數。過多的全局函數會導致對它們的管理非常困難。使用對象的一個好處是,可以把函數歸于類中。通過合理地安排DSL代碼把函數從全局作用域中移出,并且放置在更加合理的地方。
類方法和方法鏈
在面向對象語言中控制方法作用域的最簡單方式是使用類方法。但類方法卻帶來了大量重復:在每次調用類方法時都需要使用類名。我們可以通過方法鏈來減少重復,就如下面的代碼一樣。
下載 lairs/rules11.rb
Configuration.item(:secure_air_vent)
Configuration.item(:acid_bath).
uses(Resources.acid.
set_type(:hcl).
set_grade(5)).
uses(Resources.electricity(12))
Configuration.item(:camera).uses(Resources.electricity(1))
Configuration.item(:small_power_plant).
provides(Resources.electricity(11)).
depends_on(:secure_air_vent)
每個DSL子句都從一個類方法的調用開始。類方法返回一個對象,即下一個方法調用的接收者。通過這樣不斷地返回下一個調用的接收者把所有的方法調用串起來。但在有些地方使用方法鏈卻不太合適,這時就應該重新使用類方法。
讓我們更進一步地看一下這個例子,以了解它的具體實現。不過此例中有一些問題和錯誤,我會在以后探討和更正它們。首先從一個物件的定義開始吧。
下載 lairs/builder11.rb
def self.item arg
原文轉自:http://www.ituring.com.cn/article/17818