一个巢穴,二十种Ruby DSL(6)

发表于:2013-02-28来源:图灵社区作者:Martin Fowler点击数: 标签:
item :secure_air_vent item :acid_bath, :uses = [acid(:type = :hcl, :grade = 5) , electricity(12)] item :camera, :uses = electricity(1) item :small_power_plant, :provides = electricity(11), :depends_on

  item :secure_air_vent

  item :acid_bath,

  :uses => [acid(:type => :hcl,

  :grade => 5) ,

  electricity(12)]

  item :camera,

  :uses => electricity(1)

  item :small_power_plant,

  :provides => electricity(11),

  :depends_on => :secure_air_vent

  使用这种方法有利有弊。对于像小型电厂这种简单物件它非常合适。但对于如酸性溶液这种复杂物件,它却不合适。酸性溶液依赖于两种资源,所以需要把对acid和electricity的调用放在一个列表中。一旦把它们置于字面量映射中,代码就变得很不直观。

  接下来实现会变得更加复杂。对item方法的调用既需要名称,也需要映射。在Ruby中会将其视作一个name参数后面跟着一个“名称—值”对形式的多重参数。

  下载 lairs/builder4.rb

  def item name, *args

  newItem = Item.new name

  process_item_args(newItem, args) unless args.empty?

  @config.add_item newItem

  return self

  end

  process_item_args函数根据不同的键来切换处理每个闭包,要注意的是:args中的值可能是一个元素,也可能是一个列表。

  下载 lairs/builder4.rb

  def process_item_args anItem, args

  args[0].each_pair do |key, value|

  case key

  when :depends_on

  oneOrMany(value) {|i| anItem.add_dependency(@config[i])}

  when :uses

  oneOrMany(value) {|r| anItem.add_usage r}

  when :provides

  oneOrMany(value) {|i| anItem.add_provision i}

  end

  end

  end

  def oneOrMany(obj, &block)

  if obj.kind_of? Array

  obj.each(&block)

  else

  yield obj

  end

  end

  当你遇到这种情况——传入的参数值既可能是一个单独的元素,也可能是一个列表——时,始终把参数作为列表传入通常会让情况变得比较简单。

  下载 lairs/rules21.rb

  item :secure_air_vent

  item :acid_bath,

  [:uses,

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

  electricity(12)]

  item :camera,

  [:uses, electricity(1)]

  item :small_power_plant

  [:provides, electricity(11)],

  [:depends_on, :secure_air_vent]

  上面代码中item方法的参数是一个名称和一个列表(而不是散列)。列表中的第一个元素是键,其后的元素是这个键对应的值(这就是Lisp程序员用列表模拟散列的方法)。这种方法减少了嵌套层次,并且更易处理。

  下载 lairs/builder21.rb

  def item name, *args

  newItem = Item.new name

  process_item_args(newItem, args) unless args.empty?

  @config.add_item newItem

  return self

  end

  def process_item_args anItem, args

  args.each do |e|

  case e.head

  when :depends_on

  e.tail.each {|i| anItem.add_dependency(@config[i])}

  when :uses

  e.tail.each {|r| anItem.add_usage r}

  when :provides

  e.tail.each {|i| anItem.add_provision i}

  end

  end

  end

  在这里需要注意的是,我们把列表当作了一个头和尾的组合(而不是一系列元素)来处理。所以不要用只有两个元素的列表来替换散列,因为那没有任何价值。在这里我们用第一个元素为键,其他元素为值的列表替换散列,这样我们就不需要在一个集合中嵌套另一个集合。

  头和尾并非是Ruby的列表(叫做Array)默认具有的方法,但添加它们非常简单。

  下载 lairs/builder21.rb

  class Array

  def tail

  self[1..-1]

  end

  alias head firsts

  end

  在结束字面量集合的讨论之前,让我们来看一看最后版本。下面就是以使用映射为主,列表为辅的整个配置代码。

  下载 lairs/rules22.rb

  {:items => [

  {:id => :secure_air_vent},

  {:id => :acid_bath,

  :uses => [

  [:acid, {:type => :hcl, :grade => 5}],

  [:electricity, 12]]},

  {:id => :camera,

  :uses => [:electricity, 1]},

  {:id => :small_power_plant,

  :provides => [:electricity, 11],

  :depends_on => :secure_air_vent}

  ]}

  下面是只使用列表实现的版本,也叫Greenspun版本1。

  1 出自Philip Greenspun的“第十编程法则”:任何使用静态类型检查语言编写的、足够复杂的程序都包含一个特定、非正式定义、容易引入Bug且缓慢的动态检查语言实现。——译者注

  下载 lairs/rules6.rb

  [

  [:item, :secure_air_vent],

  [:item, :acid_bath,

  [:uses,

  [:acid,

  [:type, :hcl],

  [:grade, 5]],

  [:electricity, 12]]],

  [:item, :camera,

  [:uses, [:electricity, 1]]],

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