1.4 DSL的执行模型

领域专家通过DSL脚本理解领域模型和业务规则,而开发者负责实现DSL这个技术支撑平台。大多数情况下,DSL无非是覆盖于宿主语言之上的一个抽象层,向业务用户提供领域友好的界面。(其实不一定是宿主语言,详见1.5节的DSL分类。)可以这么说,你要做的事情就是对宿主语言进行扩展,在其上实现另一种语言。这种用一种语言实现另一种语言的概念有时候称为元语言学抽象。有些DSL并不通过内嵌方式实现,也许开发团队会为DSL特别设计一种定制语言。不同类型的DSL实现方式我们放到1.5节讨论,现在先来看看怎样执行DSL脚本。

图1-4展示了三种最常见的DSL脚本执行方式。

  • 脚本中的解答域模型可以直接执行,无需进行代码生成或其他变换操作。可能有一个解释器直接解释并运行脚本。UNIX中的微型编程语言awk和sed都是能直接执行的DSL。
  • 在虚拟机上开发的DSL脚本遵照第二种执行模型。任何Java DSL脚本的语义模型都生成在JVM上执行的字节码。
  • 有些语言提供编译时元编程能力。当用这样的语言开发DSL时,开发者在源代码中加入一些元结构,它们会在运行前被转译成一般语法成分。Lisp通过宏来支持这种实现手法,Lisp宏会在宏展开阶段展开成一般的Lisp成分(详见附录B)。对于这类语言,在为虚拟机生成字节码之前,存在一个对源代码进行转译的中间阶段。

enter image description here

图1-4 DSL脚本的三种执行模型。实现了解答域模型的程序可以直接执行❶;可以编成字节码后执行❷;可以先对源代码进行转译(像Lisp宏),然后生成字节码再执行❸

熟悉三种最常见的DSL脚本执行模型之后,我们回头看看代码清单1-1中老鲍摆弄的那段DSL。你会发现,不管选择什么样的实现语言,DSL脚本下面必定有一个语义模型作支撑。这个语义模型可以是Ruby或Scala之类的宿主语言,也可以是工作在蹦蹦高证券公司的程序员专为金融交易DSL设计的一种定制语言。

以常用的构建工具Ant为例,它向用户提供了一种基于XML的DSL。当看到下面的XML片段时,开发者会认为它表达的都是一些熟悉的概念。我们很容易看懂它的任务目标,即构建一个jar,而这个任务依赖于compile任务。

  1. <target name="jar" depends="compile">
  2. <mkdir dir="${build.dist}"/>
  3. <jar jarfile="${build.dist}/${name}-${version}.jar">
  4. <fileset dir="${build.classes}" includes="**"/>
  5. <fileset dir="${src.dir}">
  6. <include name="*"/>
  7. </fileset>
  8. </jar>
  9. </target>

这段DSL脚本的背后有一个语义模型;其实现用Java类、方法和包的形式建立“任务”、“依赖”等界面。开发者在使用Ant时不需要越过DSL接口去深究Ant的内部实现;当然,因为Ant是一个可扩展的框架,所以还是存在例外情况,但也仅仅不过是例外。

目前为止,我们讨论的DSL脚本主要是通过扩展宿主语言来设计的,但DSL脚本并不只这一种类型。DSL还可以按其实现方式进行分类。下一节将阐述DSL的分类法。