一個巢穴,二十種Ruby DSL(5)

發表于:2013-02-28來源:圖靈社區作者:Martin Fowler點擊數: 標簽:
def run item(:secure_air_vent) item(:acid_bath). uses(acid. type(:hcl). grade(5)). uses(electricity(12)) item(:camera).uses(electricity(1)) item(:small_power_plant). provides(electricity(11)). depends

  def run

  item(:secure_air_vent)

  item(:acid_bath).

  uses(acid.

  type(:hcl).

  grade(5)).

  uses(electricity(12))

  item(:camera).uses(electricity(1))

  item(:small_power_plant).

  provides(electricity(11)).

  depends_on(:secure_air_vent)

  end

  end

  把DSL代碼放置到一個子類中,允許我們做一些在全局執行上下文中無法做到的事情。比如我們可以拋棄方法鏈來實現對item的連續調用,因為我們可以讓item成為配置生成器中的一個方法。同樣,我們可以把acid和electricity定義成配置生成器中的方法,以此來避免靜態工廠類的使用。

  但這么做的缺點是DSL文本上將會出現一些額外的類、方法頭和尾。

  此例演示了如何在一個對象實例的上下文中執行DSL代碼。這非常有用,因為對象實例的上下文允許我們訪問實例中的變量。我們也可以通過類方法在一個類上下文中這么做。通常我更喜歡使用實例上下文,因為它允許我們創建一個生成器實例,并且在執行完DSL代碼后就可拋棄這個實例。這樣可以保證兩個執行環境的相互隔離,以避免殘留數據相互干擾的風險(特別是在需要處理并發時)。

  而Ruby提供了一個兩全其美的方法:Ruby中有個方法叫做instance_eval,它可以接收一段代碼——一個字符串或者是一個塊,然后在一個對象上下文中執行這段代碼。這使得我們只需要在文件中保存DSL代碼,而仍能把代碼置于一個對象上下文中執行。

  下載 lairs/rules1.rb

  item :secure_air_vent

  item(:acid_bath).

  uses(acid.

  type(:hcl).

  grade(5)).

  uses(electricity(12))

  item(:camera).uses(electricity(1))

  item(:small_power_plant).

  provides(electricity(11)).

  depends_on(:secure_air_vent)

  Ruby在支持閉包的同時也支持執行上下文的更改,這使得我們可以把擁有閉包的代碼傳給instance_eval,然后在一個對象實例中執行。用這種方式寫就的代碼如下。

  下載 lairs/rules18.rb

  item :secure_air_vent

  item(:acid_bath) do

  uses(acid) do

  type :hcl

  grade 5

  end

  uses(electricity(12))

  end

  item(:camera) do

  uses(electricity(1))

  end

  item(:small_power_plant) do

  provides(electricity(11))

  depends_on(:secure_air_vent)

  end

  結果是很具吸引力的。這段代碼具有閉包結構,且作為顯式接收者的塊參數沒有重復。然而這種技術的使用需要格外小心,因為塊上下文的切換容易引起很多混亂。在每個塊中偽變量self指向不同的對象,這會迷惑DSL編寫者,特別是需要從塊中獲取標準的self時。

  這種混亂在實際應用中已被證實。Ruby的生成器庫在早期時使用了instance_eval,但實踐中卻發現它會引起混亂并且難以使用。Jim Weirich(Ruby生成器庫的作者)總結道:如果DSL編寫者是程序員,像這樣切換執行上下文對他們而言是個壞消息,因為它違背了我們對宿主語言的期望(這個擔心引起了其他Ruby DSL編寫者的共鳴)。而對于非程序員的DSL編寫者來講這不是一個很大的問題,因為他們本來就沒有這種期待。我個人的感覺是:內部DSL跟宿主語言的集成度越高,就越應該避免這種違背正常期望的行為。而對于一些沒有必要跟宿主語言很相似的迷你語言(mini-languages),就比如本章中的這個配置例子,應該以易讀性為重。

  3.6 字面量集合

  對內部DSL而言,函數調用語法是一個很重要的結構機制。事實上對很多語言而言,函數調用基本上是唯一的結構機制。而在有些語言中有另一個很有用的機制:在表達式中使用字面量集合。但在很多語言中這個機制受到局限,或者是因為沒有簡單的語法,或者是因為不能在合適的地方使用它們。

  有兩種非常有用的字面量集合:列表和映射(也可以叫散列、字典和關聯數組)。大多數現代語言都在庫中提供了這樣的對象,以及用來處理這些對象的合適的API。用這兩個結構編寫DSL都非常方便,雖然有些Lisp程序員會告訴你可以通過列表來模擬映射。

  下面就是一個用字面量集合來定義acid的例子。

  下載 lairs/rules20.rb

  item :secure_air_vent

  item(:acid_bath) do

  uses(acid(:type => :hcl, :grade => 5))

  uses(electricity(12))

  end

  item(:camera) do

  uses(electricity(1))

  end

  item(:small_power_plant) do

  provides(electricity(11))

  depends_on(:secure_air_vent)

  end

  上述代碼混合使用了函數調用和字面量集合,并且利用了Ruby可以在沒有二義性時清除參數括弧的特性。acid的函數現在看起來是這樣的。

  下載 lairs/builder20.rb

  def acid args

  result = Acid.new

  result.grade = args[:grade]

  result.type = args[:type]

  return result

  end

  用一個字面量散列作為參數是Ruby一項慣例(這是它受Perl的影響之一)。這種方式對于有些函數非常合適,比如有很多可選參數的創建方法。應用于這個例子,這種方式不僅提供了一個干凈清晰的DSL語法,而且避免了acid和electricity生成器的使用——取而代之以直接創建需要的對象。

  如果更進一步利用字面量集合,會出現什么情況?比如當我們把對uses、privides和depends_on這些函數的調用都替換成映射時。

  下載 lairs/rules4.rb

原文轉自:http://www.ituring.com.cn/article/17818

国产97人人超碰caoprom_尤物国产在线一区手机播放_精品国产一区二区三_色天使久久综合给合久久97