[: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