DSL其实并没有那么神秘。实际上,在平时的面向对象的编程中,大家会自觉不自觉的使用DSL的一些方法和技巧。比如,如果我们定义了非常面向业务的函数,然后这些函数的集合就可以被称为一种DSL了。Smalltalk的开发者Blaine Buxton 这样评价DSL:"I believe a DSL is a healthybi-product of a good object-oriented design"。
然而,也正是因为DSL的构建可以像定义函数一样简单,这也让很多人觉得DSL不过就是定义一些合适的函数——如果那些函数在调用时不用带上括号的话那就更好了。诚然,DSL和定义合适的函数之间是有一些类似之处,但是这恰恰是同一个问题的两面,DSL更多是从客户的角度出发看待代码,定义函数则更多的从解决问题的方案的角度看待代码。诚然两者都有交集,但是出发点却截然不同。
我引用Obie Fernandez在QCon上举的一个例子来看看定义函数和定义DSL的差别。
场景:如果你想要一杯咖 啡。。。
你可以定义好一个叫做Latte的类,然后:
如果你不是这个类的设计者,你会问,第一个参数是什么意思?第二个呢?第三个?还有,那个false谁能给点提示?
或者你也可以这样:
我只要告诉你,order等号后面,按照你想要的类型去点就可以了。
从上面的例子上,我们大体可以看出定义函数和DSL之间的风格的差别了。相比较而言,DSL的风格更面向客户,语法也更加友好。有人会问:“我为什么要用DSL?DSL都是给不懂编程的人用的。”非也。DSL说到底是为了更加快速的描述问题,解决问题的。像Capistrano(一种网络应用的部署解决方案)用DSL来定义部署操作:
即便我们没有学过Capistrano的语法,从上面的文件中我们也可以大体了解这些操作做了什么。值得一提的是,上面的文件并不是什么简化的说明文档,而是实实在在可以运行的代码。
又比如Rake(Ruby版的Makefile):
对于熟悉Ruby的人来说,这个DSL基本就是Ruby的语法了,但是可以通过一些关键字完成更为强大的功能。
按照MartingFowler的看法,DSL可以分为两种基本类型:内部DSL和外部DSL。顾名思义,外部DSL就相当于实现一种编程语言,也许不如实现一门通用语言那么复杂,但是工作量不小;内部DSL就是基于一种通用编程语言的基础上进行关键字的定义封装来达到DSL的目的,这种DSL的扩展性可能会受到母语言的影响,对于不熟悉母语言的人来说可能不是那么好理解,不过好处就是你可以利用母语言本身通用的其他功能。那么Ruby DSL自然是一种内部DSL了。
DSL分 为内部DSL和 外部DSL, 我们再看一遍他们的定义:1. External DSLs 用 不同于host语言的语言来编写,通过编译和解释器来翻译成host语 言2. Internal DSLs 将host语 言转化为DSL本身。
第 二种方式,相比较第一种来说,构建DSL更 为简便,还可以利用host语 言本身已有的语言特征和库等,缺点是定义DSL的时候会受到host语 言的限制。但是,如果我们选择了一门语法友好、灵活的编程语言作为host语 言的话,那么我们就可以放大内部DSL的优点,弱化它的缺点,最终达到效率和回报的最佳值,那么Ruby是 一个好的选择。
Why Ruby?Obie Fernandez在InfoQ论 坛上列出了Ruby的一些feature:
当 然了,Ruby的 语法有很多值得玩味的地方,比如。。。(也 许这需要好几篇文章来写了)。 好吧,让我们直接一点:Ruby DSL的强大直接来自于Ruby强 大的元编程能力。
“元”这个词来源于希腊语中表示”…之 间, …之后, …超 越’的前缀”meta”,有”超越”和”高阶”的意思。正如”元数据”表示”数据的数据”, “元模 型”表示”模型的 模型”, “元编程”表示”编程的编程”。
在《programming ruby 1.9》中说过:”Programming is all about building layersof abstractions.”有些语言,比如C语言,更接近与机器本身,从而导致C语言跟应用之间的距离比较遥远。而有些语言,比如Ruby,提供了更高层次的抽象,从而让你在更接近于特定领域的地方开始编程。但是,当你使用元编程时,你就不再局限于语言本身的抽象层次了,你可以基于母语言构建新的抽象。于是,” In effect, you’recreating a new, domain-specific programming language—one that’sdesigned to let you express the concepts you need to solve yourparticular problem.”
所以,从这个角度看,元编程是DSL的基础。几乎每一种Ruby DSL的实现都离不开元编程。下面我归纳一下RubyDSL的几种方式以及其中利用到的元编程技巧:
1. Class Marcos
你 可以把”ofter_create”, ”has_many”看做一种声名。它的实现依赖于元编程中反射功能中的define_method或者instance_eval,可以为函数动态添加方法。
2. Methods on an Object
=>
这 是一种构造层次数据的DSL, 完全基于Builder::XmlMarkup的对象。我们可以发现,生成的XML中定义的关键字完全可以由对象的方法名来决定,但是这个对象是如何做到满足所有任意添加的关键字呢?这其中的实现就依赖于元编程中反射功能中的method_missing方法。
3. 自 定义词汇
在Ruby on Rails中,你可以:
4.years +13.days + 2.hours
或 者我们可以为这些词汇定义上下文:
第 一种你可以叫做”自定义词汇”, 第二种你可以叫做”单位的实现”,第三种你可以叫做”上下文相关的词汇”,但是本质上他们都是一样的。由于Ruby中没有函数,只有方法,意味着所有方法的使用都关联到一个对象。第一种实现,实际上调用者是Object的对象,第二种,调用者是4,13,2这些Fixnum的对象,第三种实现通过关键字task之后的关键字来调用不同的对象来调用这些方法。
这些DSL的实现依赖于Ruby开 放类,block(其实相当于高阶函数)等特性。
基本上Ruby的DSL都是基于以上三种模式的组合和扩展,当然其实现背后的技巧可以非常不同和巧妙。接下来我们会结合实例来看看这些类型的DSL是如何实现的。