什么是DSL
DSL旨在解决特定类型的问题。 它们是很短的表达方式,很适合狭窄的环境。 例如,对于GORM,您可以使用DSL而不是XML来表示休眠映射。
static mapping = {
table 'person'
columns {
name column:'name'
}
}
关于DSL的许多理论及其带来的好处已得到充分证明。 请以以下来源为起点:
Groovy中的一个简单DSL示例
以下示例提供了实现内部DSL的简化视图。 框架具有创建DSL的更高级的方法。 但是,此示例确实强调了封闭委托和元对象协议概念,这对于理解DSL的内部工作至关重要。
想象一个客户需要一个备忘录生成器。 备忘录需要有一些简单的字段,例如“至”,“来自”和“正文”。 备忘录也可以包含“摘要”或“重要”等部分。 摘要字段是动态的,可以按需输入。 另外,备忘录需要以三种格式输出:xml,html和文本。
我们选择在Groovy中将其实现为DSL。 DSL结果如下所示:
MemoDsl.make {
to 'Nirav Assar'
from 'Barack Obama'
body 'How are things? We are doing well. Take care'
idea 'The economy is key'
request 'Please vote for me'
xml
}
代码的输出产生:
<memo>
<to>Nirav Assar</to>
<from>Barack Obama</from>
<body>How are things? We are doing well. Take care</body>
<idea>The economy is key</idea>
<request>Please vote for me</request>
</memo>
DSL中的最后一行也可以更改为“ html”或“ text”。 这会影响输出格式。
实作
接受闭包的静态方法是实现DSL的简便方法。 在备忘录示例中,类MemoDsl具有make方法。 它创建一个实例,并将闭包中的所有调用委托给该实例。 这是“ to”和“ from”部分最终在MemoDsl类内部执行方法的机制。 调用to()方法后,我们会将文本存储在实例中以供以后格式化。
class MemoDsl {
String toText
String fromText
String body
def sections = []
/**
* This method accepts a closure which is essentially the DSL. Delegate the
* closure methods to
* the DSL class so the calls can be processed
*/
def static make(closure) {
MemoDsl memoDsl = new MemoDsl()
// any method called in closure will be delegated to the memoDsl class
closure.delegate = memoDsl
closure()
}
/**
* Store the parameter as a variable and use it later to output a memo
*/
def to(String toText) {
this.toText = toText
}
def from(String fromText) {
this.fromText = fromText
}
def body(String bodyText) {
this.body = bodyText
}
}
动态部分
当闭包包含MemoDsl类中不存在的方法时,groovy会将其标识为缺少的方法。 使用Groovy的元对象协议,将调用类上的methodMissing接口。 这就是我们处理备忘录部分的方式。 在上面的客户代码中,我们有想法和请求条目。
MemoDsl.make {
to 'Nirav Assar'
from 'Barack Obama'
body 'How are things? We are doing well. Take care'
idea 'The economy is key'
request 'Please vote for me'
xml
}
这些部分在MemoDsl中使用以下代码进行处理。 它创建一个节类并将其附加到实例中的列表中。
/**
* When a method is not recognized, assume it is a title for a new section. Create a simple
* object that contains the method name and the parameter which is the body.
*/
def methodMissing(String methodName, args) {
def section = new Section(title: methodName, body: args[0])
sections << section
}
处理各种输出
最后,DSL最有趣的部分是我们如何处理各种输出。 闭包中的最后一行指定所需的输出。 当闭包包含不带参数的字符串(例如“ xml”)时,groovy会假定这是一种“ getter”方法。 因此,我们需要实现“ getXml()”以捕获委托执行:
/**
* 'get' methods get called from the dsl by convention. Due to groovy closure delegation,
* we had to place MarkUpBuilder and StringWrite code in a static method as the delegate of the closure
* did not have access to the system.out
*/
def getXml() {
doXml(this)
}
/**
* Use markupBuilder to create a customer xml output
*/
private static doXml(MemoDsl memoDsl) {
def writer = new StringWriter()
def xml = new MarkupBuilder(writer)
xml.memo() {
to(memoDsl.toText)
from(memoDsl.fromText)
body(memoDsl.body)
// cycle through the stored section objects to create an xml tag
for (s in memoDsl.sections) {
'$s.title'(s.body)
}
}
println writer
}
html和text的代码非常相似。 唯一的变化是如何格式化输出。
整个代码
接下来将显示完整的代码。 我发现最好的方法是先设计DSL客户端代码和指定的格式,然后解决实现问题。 我使用TDD和JUnit来驱动我的实现。 请注意,尽管可以很容易地做到这一点,但我并没有费力去对测试中的系统输出进行断言。 该代码在任何IDE中都是完全可执行的。 运行各种测试以查看DSL输出。
package com.solutionsfit.dsl.memotemplate
class MemolDslTest extends GroovyTestCase {
void testDslUsage_outputXml() {
MemoDsl.make {
to 'Nirav Assar'
from 'Barack Obama'
body 'How are things? We are doing well. Take care'
idea 'The economy is key'
request 'Please vote for me'
xml
}
}
void testDslUsage_outputHtml() {
MemoDsl.make {
to 'Nirav Assar'
from 'Barack Obama'
body 'How are things? We are doing well. Take care'
idea 'The economy is key'
request 'Please vote for me'
html
}
}
void testDslUsage_outputText() {
MemoDsl.make {
to 'Nirav Assar'
from 'Barack Obama'
body 'How are things? We are doing well. Take care'
idea 'The economy is key'
request 'Please vote for me'
text
}
}
}
package com.solutionsfit.dsl.memotemplate
import groovy.xml.MarkupBuilder
/**
* Processes a simple DSL to create various formats of a memo: xml, html, and text
*/
class MemoDsl {
String toText
String fromText
String body
def sections = []
/**
* This method accepts a closure which is essentially the DSL. Delegate the closure methods to
* the DSL class so the calls can be processed
*/
def static make(closure) {
MemoDsl memoDsl = new MemoDsl()
// any method called in closure will be delegated to the memoDsl class
closure.delegate = memoDsl
closure()
}
/**
* Store the parameter as a variable and use it later to output a memo
*/
def to(String toText) {
this.toText = toText
}
def from(String fromText) {
this.fromText = fromText
}
def body(String bodyText) {
this.body = bodyText
}
/**
* When a method is not recognized, assume it is a title for a new section. Create a simple
* object that contains the method name and the parameter which is the body.
*/
def methodMissing(String methodName, args) {
def section = new Section(title: methodName, body: args[0])
sections << section
}
/**
* 'get' methods get called from the dsl by convention. Due to groovy closure delegation,
* we had to place MarkUpBuilder and StringWrite code in a static method as the delegate of the closure
* did not have access to the system.out
*/
def getXml() {
doXml(this)
}
def getHtml() {
doHtml(this)
}
def getText() {
doText(this)
}
/**
* Use markupBuilder to create a customer xml output
*/
private static doXml(MemoDsl memoDsl) {
def writer = new StringWriter()
def xml = new MarkupBuilder(writer)
xml.memo() {
to(memoDsl.toText)
from(memoDsl.fromText)
body(memoDsl.body)
// cycle through the stored section objects to create an xml tag
for (s in memoDsl.sections) {
'$s.title'(s.body)
}
}
println writer
}
/**
* Use markupBuilder to create an html xml output
*/
private static doHtml(MemoDsl memoDsl) {
def writer = new StringWriter()
def xml = new MarkupBuilder(writer)
xml.html() {
head {
title('Memo')
}
body {
h1('Memo')
h3('To: ${memoDsl.toText}')
h3('From: ${memoDsl.fromText}')
p(memoDsl.body)
// cycle through the stored section objects and create uppercase/bold section with body
for (s in memoDsl.sections) {
p {
b(s.title.toUpperCase())
}
p(s.body)
}
}
}
println writer
}
/**
* Use markupBuilder to create an html xml output
*/
private static doText(MemoDsl memoDsl) {
String template = 'Memo\nTo: ${memoDsl.toText}\nFrom: ${memoDsl.fromText}\n${memoDsl.body}\n'
def sectionStrings =''
for (s in memoDsl.sections) {
sectionStrings += s.title.toUpperCase() + '\n' + s.body + '\n'
}
template += sectionStrings
println template
}
}
package com.solutionsfit.dsl.memotemplate
class Section {
String title
String body
}
参考: Groovy DSL –来自我们的JCG合作伙伴 Nirav Assar的简单示例 ,在Assar Java Consulting博客上。
翻译自: https://www.javacodegeeks.com/2012/08/groovy-dsl-simple-example.html