Ruby在最近得以流行的主要原因是它非常适合用来编写内部领域特定语言(Internal DSL)。内部领域特定语言是指在另一种语言(宿主语言)之上编写的领域特定语言。目前,用Ruby编写DSL更有日趋火爆的迹象。
内部领域特定语言是一个在Lisp圈子里一直非常流行的想法。很多Lisp语言拥护者炮轰Ruby在这方面没有带来任何新意。但令Ruby与众不同的一点是:它提供了多种技术来开发内部DSL。虽然Lisp也提供了一些很好的机制,但相对于Ruby,它的选择还是较少的。
本章通过一个例子来探讨这些技术,以使你对这些技术有所体会,并让你能在它们之间进行比较取舍。
3.1 巢穴
接下来,我将用一个简单的例子来探讨这些技术。这个例子是一个常见但很有趣的抽象配置问题。在各种各样的设备中,我们都会遇到这样的问题:如果要有x,那么必须要有一个相匹配的y。当我们购买电脑、安装软件或做其他一些更加有趣的事情时,都会遇到这个问题。
在这个例子中,让我们想象有这么一家公司,它专门将复杂的设备提供给那些脑子里整天想着要征服世界的狂徒。从此类电影的数量来看,这是一个很大的市场。而这些狂徒藏身的巢穴,被一些极具魅力秘密特工不断捣毁,更增加了对这些设备的持续需求。
本章将用DSL来描述这些狂徒们放置在巢穴中设备的配置规则。此例中的DSL将会描述两类事物:物件(item)和资源(resource)。物件表示一些具体的事物,比如摄像机(camera)和酸性溶液(acid bath)等;而资源表示一些大量的原材料,比如电(electricity)等。
此例涉及两种资源:电和酸(acid)。假设每种资源都拥有多种不同的属性。比如,所有物件的电力都由巢穴中的电厂供应(这些狂徒们可不想使用社会公共服务)。所以在这个抽象表示中,每件资源都需要有它自己独立的类。
为了更好地描述这个问题,让我们假设只有两种类别的资源:简单的和复杂的。简单资源(比如电)只有为数不多且数量固定的属性,所以可以在生成函数的参数中传入这些属性。而复杂资源(比如酸)有很多可选的属性,它们需要一些单独的设置方法来设置属性。虽然此例中的酸实际上只有两种属性,但不妨让我们把它想象成拥有几十种不同属性的复杂资源。
而关于物件,有三个特点需要声明:它们使用资源,它们提供资源,并且它们依赖于巢穴中的其他物件。
好了,不继续吊你的胃口了,让我们马上来看看这个抽象表示的实现吧。注意,在所有将要讨论的例子中,都将使用同一个抽象表示。
下载 lairs/model.rb
class Item
attr_reader :id, :uses, :provisions, :dependencies
def initialize id
@id = id
@uses = []
@provisions = []
@dependencies = []
end
def add_usage anItem
@uses << anItem
end
def add_provision anItem
@provisions << anItem
end
def add_dependency anItem
@dependencies << anItem
end
end
class Acid
attr_accessor :type, :grade
end
class Electricity
def initialize power
@power = power
end
attr_reader :power
end
把所有的特定配置放在一个配置对象中。
下载 lairs/model.rb
class Configuration
def initialize
@items = {}
end
def add_item arg
@items[arg.id] = arg
end
def [] arg
return @items[arg]
end
def items
@items.values
end
end
为了描述清楚本章所要阐述的问题,我们只需定义少量物件以及它们的规则:
一种使用12单位电和五级盐酸的酸性溶液;
一个使用1单位电的摄像机;
一个供应11单位电并且依赖于一个安全排气口(secure air vent)的小型电厂(small power plant)。
在抽象表示中可以这样描述这些物件的规则。
下载 lairs/rules0.rb
config = Configuration.new
config.add_item(Item.new(:secure_air_vent))
config.add_item(Item.new(:acid_bath))
config[:acid_bath].add_usage(Electricity.new(12))
acid = Acid.new
config[:acid_bath].add_usage(acid)
acid.type = :hcl
acid.grade = 5
config.add_item(Item.new(:camera))
config[:camera].add_usage(Electricity.new(1))
config.add_item(Item.new(:small_power_plant))
config[:small_power_plant].add_provision(Electricity.new(11))
config[:small_power_plant].add_dependency(config[:secure_air_vent])
虽然能形成可用的配置但这样的代码并不流畅。下面,我们将尝试用不同的方法来编写代码,以更好地描述这些规则。
3.2 使用全局函数
函数是程序最基本的结构,它是生成软件和为程序引入领域名称的最简单方式。
所以,我们编写DSL的初次尝试就是调用一系列的全局函数。
下载 lairs/rules8.rb
item(:secure_air_vent)
item(:acid_bath)
uses(acid)
acid_type(:hcl)
acid_grade(5)
uses(electricity(12))
item(:camera)
uses(electricity(1))
item(:small_power_plant)
原文转自:http://www.ituring.com.cn/article/17818