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

發表于:2013-02-28來源:圖靈社區作者:Martin Fowler點擊數: 標簽:
new_item = Item.new(arg) @@current.add_item new_item return new_item end 這個方法創建了一個新物件,然后把它置于一個存儲配置信息的類變量中,最后返回這個新創建的

  new_item = Item.new(arg)

  @@current.add_item new_item

  return new_item

  end

  這個方法創建了一個新物件,然后把它置于一個存儲配置信息的類變量中,最后返回這個新創建的物件。最后的物件返回是這里的關鍵,因為它建立起了方法鏈。

  下載 lairs/builder11.rb

  def provides arg

  add_provision arg

  return self

  end

  這個provides方法只是調用了一下add方法,然后立即返回它本身以讓方法鏈得以延續。其他方法也大多類似。

  方法鏈的使用與很多好的編程準則是相矛盾的。很多語言都約定修飾符(改變對象狀態的方法)不得返回任何東西,這遵循了命令—查詢分離原則(command query separation principle)——一個在大多數時候都值得遵守的原則。但不幸的是它跟流式的內部DSL相悖。DSL編寫者為了支持方法鏈經常會拋棄這個準則。同時,此例也使用了方法鏈來設置酸的類型(type)和級別(grade)。

  另一個與常規編碼準則大有區別的地方是代碼格式。在這個例子中,代碼被刻意編排成層級關系突出以適應DSL的需求。在使用方法鏈時,你會經??吹椒椒ㄕ{用會換行。

  除了演示如何編寫方法鏈,此例還展示了如何使用工廠類來創建資源。我們并不是往Electricity類中添加創建資源的方法,而是定義一個資源類,資源類包含創建電和酸實例的類方法。這種工廠經常被稱為類工廠或者靜態工廠,因為它們只包含用于創建對應對象的類(靜態)方法。類工廠使得DSL更加易讀,而且避免了需要在領域類中添加一些額外方法的問題。

  這更加突出了這段DSL代碼的一個問題:為了讓這段代碼工作,我們需要在領域類中添加許多方法——而這些方法放置在領域類中并不合適。一個對象中的大多數方法都應該在獨立的調用中具有意義,但DSL方法的編寫是為了讓方法在DSL表達式中更有意義。所以,DSL代碼和普通代碼需要遵循不同的原則,比如命名和命令-查詢分離等原則。此外,DSL方法跟上下文密切相關,并且只能用于DSL表達式中來創建對象?;旧?,編寫DSL方法需要遵循的原則和普通方法是不一樣的。

  表達式生成器

  避免DSL和常規API沖突的一個方法是使用表達式生成器模式(Expression Builder pattern)。這個模式的本質是把DSL方法放置在用于創建真實領域對象的一個獨立對象中。使用表達式生成器模式的方式有兩種。其中一種方式是保持DSL代碼不變,但用DSL方法創建生成器對象而非領域對象。

  可以通過讓原來的類方法返回一個不同的物件生成器對象來實現這種方式。

  下載 lairs/builder12.rb

  def self.item arg

  new_item = ItemBuilder.new(arg)

  @@current.add_item new_item.subject

  return new_item

  end

  物件生成器提供了DSL代碼所需的方法,然后把這些方法的調用轉發給物件對象。

  下載 lairs/builder12.rb

  attr_reader :subject

  def initialize arg

  @subject = Item.new arg

  end

  def provides arg

  subject.add_provision arg.subject

  return self

  end

  當然,編寫代碼時我們可以完全擺脫領域對象的API,讓DSL看起來更加清晰。

  下載 lairs/rules14.rb

  ConfigurationBuilder.

  item(:secure_air_vent).

  item(:acid_bath).

  uses(Resources.acid.

  type(:hcl).

  grade(5)).

  uses(Resources.electricity(12)).

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

  item(:small_power_plant).

  provides(Resources.electricity(11)).

  depends_on(:secure_air_vent)

  上面的代碼從一個生成器的使用開始,然后使用生成器本身的方法鏈。這不僅避免了重復,而且避免了討厭的類變量的使用。第一個調用是調用配置生成器的類方法來創建一個配置生成器實例:

  下載 lairs/builder14.rb

  def self.item arg

  builder = ConfigurationBuilder.new

  builder.item arg

  end

  def initialize

  @subject = Configuration.new

  end

  def item arg

  result = ItemBuilder.new self, arg

  @subject.add_item result.subject

  return result

  end

  創建一個配置生成器,緊接著即調用新創建物件的實例方法。這個實例方法創建了一個新的物件生成器,并且把它返回以作進一步的調用。在這里稍微有點怪異的是一個類方法和一個實例方法使用了相同的名字。為了避免引起混亂,通常我不會這樣做。但我又一次打破了慣例,因為它會讓DSL看起來更加流暢。此物件生成器具有和之前一樣的獲取物件信息的方法。另外,它需要一個物件方法來定義一個新物件。

  下載 lairs/builder14.rb

  def item arg

  @parent.item arg

  end

  def initialize parent, arg

  @parent = parent

  @subject = Item.new arg

  end

  為了在需要時重新回到配置生成器,在創建物件生成器時傳入配置生成器作為它的父生成器。同時這也是為了讓物件生成器在記錄依賴時能查找到其他物件。

  下載 lairs/builder14.rb

  def depends_on arg

  subject.add_dependency(configuration[arg])

  return self

  end

  def configuration

  return @parent.subject

  end

  而之前我需要從全局變量或者類變量中查找其他物件。

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

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