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

发表于:2013-02-28来源:图灵社区作者:Martin Fowler点击数: 标签:
[:item, :small_power_plant, [:provides, [:electricity, 11]], [:depends_on, :secure_air_vent]]] 可变参数方法 有些语言支持可变参数方法,此时在函数调用中使用字面量列表是一

  [:item, :small_power_plant,

  [:provides, [:electricity, 11]],

  [:depends_on, :secure_air_vent]]]

  可变参数方法

  有些语言支持可变参数方法,此时在函数调用中使用字面量列表是一种很有用的技术。在下面的代码中,我把这种方式应用于uses方法。

  下载 lairs/rules24.rb

  item :secure_air_vent

  item(:acid_bath) do

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

  electricity(12))

  end

  item(:camera) do

  uses(electricity(1))

  end

  item(:small_power_plant) do

  provides(electricity(11))

  depends_on(:secure_air_vent)

  end

  在这种需要把方法调用中的列表组合在一起的情况下,使用可变参数是非常趁手的——尤其是当语言对在何处放置字面量列表限制得非常严格时。

  3.7 动态接收

  动态编程语言的一个特性是:它能对没有在接收对象里定义的方法调用进行响应。

  让我们在这个例子中探索一下这句话的含义。到目前为止,我们假设巢穴里的资源数量是相对固定的,我们可以编写相应的代码来处理这些固定数量的资源。但如果不是假设的这种情况呢?如果有很多资源呢?如果需要把非常多的资源置于配置中呢?

  下载 lairs/rules23.rb

  resource :electricity, :power

  resource :acid, :type, :grade

  item :secure_air_vent

  item(:acid_bath).

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

  uses(electricity(:power => 12))

  item(:camera).

  uses(electricity(:power => 1))

  item(:small_power_plant).

  provides(electricity(:power => 11)).

  depends_on(:secure_air_vent)

  electricity和acid仍旧是生成器中的方法。我希望这些方法可以创建新定义的资源,但我不希望去定义这些方法,而是希望它们能够根据资源的数据来自动创建。

  在Ruby中可以通过重写method_missing方法来实现。在Ruby中,如果一个对象接收了一个没有定义的方法调用,那么它就会执行method_missing方法。这个方法默认是从Object类中继承而来,而且会抛出一个异常。我们可以通过重写这个方法来做一些有趣的事情。

  首先做好对资源方法调用的准备工作。

  下载 lairs/builder23.rb

  def resource name, *attributes

  attributes << :name

  new_resource = Struct.new(*attributes)

  @configuration.add_resource_type name, new_resource

  end

  Ruby有一个用来创建匿名类的工具,叫做struct。当需要一个资源时,调用struct进行定义。以resource方法的第一个参数作为新定义资源的名字,并根据随后的参数设置新定义资源的属性(property)。然后把这些新定义的资源保存于配置中。

  接着我重写了method_missing方法。该方法在一个字面量字典中遍历了所有新定义的资源,以确定方法名是否与新的struct之一对应。如果有,则加载这个struct。

  下载 lairs/builder23.rb

  def method_missing sym, *args

  super sym, *args unless @configuration.resource_names.include? sym

  obj = @configuration.resource_type(sym).new

  obj[:name] = sym

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

  obj[key] = value

  end

  return obj

  end

  在method_missing被调用时,首先确认是否有资源可以响应这个调用。如果没有,则调用超类中的method_missing以引发一个异常。

  大多数动态语言都可以重写“处理未知调用的方法”。这是一个很强大的技术,但在使用时需要格外小心,因为它会改变程序方法分派系统的机制。如果没有合理使用,代码会变得难以理解。

  Ruby的生成器库(由Jim Weirich编写)就是一个如何正确使用method_missing的好例子。生成器库是用来生成XML标记的,它非常合理地使用了闭包和method_missing。

  可以通过一个简单的例子来说明这一点。如下代码,

  下载 lairs/frags

  Builder::XmlMarkup.new("" , 2)

  puts builder.person do |b|

  b.name("jim")

  b.phone("555-1234" , "local" =>"yes")

  b.address("Cincinnati")

  end

  会生成下面这段标记。

  下载 lairs/frags

  

  jim

  555-1234

  

Cincinnati

  

  3.8 总结

  两年之前,Dave Thomas在他的博客中提及“代码拆招”(code katas)的概念:在尝试使用各种方法来解决一个简单问题的过程中,研究和比较各种解决方案的优劣。本章就是这样的一个练习。最后我也没有给出任何确定的结论,但它确实带着我们探讨和领略了用Ruby编写内部DSL的各种方法(当然,大部分方法也可以通过其他语言来实现)。

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