Groovy DSL –一个简单的例子

本文介绍了如何使用Groovy构建一个领域特定语言(DSL),以创建一个备忘录生成器。备忘录包含“至”、“来自”、“正文”等字段,支持“摘要”和“重要”等动态部分,且能以XML、HTML和文本三种格式输出。DSL通过闭包实现,利用Groovy的元对象协议处理未定义的方法,并通过静态`make`方法接受闭包,将调用委托给实例。文章还展示了如何处理不同的输出格式以及实现`methodMissing`接口以处理动态部分。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

领域专用语言(DSL)已成为Groovy习语的重要组成部分。 DSL用于本地Groovy构建器,Grails和GORM,以及测试框架。 对于开发人员而言,DSL是易用且易于理解的,与传统编程相比,这使实现更加流畅。 但是如何实现DSL? 它在后台如何工作? 本文将演示一个简单的DSL,它可以使开发人员开始了解基本概念。

什么是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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值