首页 / 软件开发 / 数据结构与算法 / 演化架构和紧急设计 - 使用Groovy构建DSL
演化架构和紧急设计 - 使用Groovy构建DSL2010-10-28 IBM Neal Ford在 上个月的这一专栏 中,我讲述了使用特定领域语言(DSL)的示例,在您的代码中定义为通用设计习惯。(我在 “组合方法和 SLAP” 一文中介绍了惯用模式的概念。)DSL 是捕获模式的一个良好介质,因为它们是声明式的,比 “普通” 源代码更容易阅读,使您的捕获模式从周围的代码中脱颖而出。构建 DSL 的语言技术通常使用巧妙的方法来为您的代码隐式地提供包装上下文。换句话说,DSL 试图使用潜在语言特性 “隐藏” 杂乱的语法来使您的代码更具可读性。尽管您可以使用 Java 语言构建 DSL,但是 DSL 用于隐藏上下文的贫乏的构造,及其死板和无常的语法,使它不适合这一技术。但是其他基于 JVM 的语言可以填补这一空缺。在本期以及下一期中,我将向您介绍如何扩展您的 DSL 构建调板,包含更富于表现力的在 Java 平台上运行的语言,从 Groovy 开始。Groovy 提供各种特性使构建 DSL 更为容易,在 DSL 中支持数量是一个常见的需求。人们总是需要很多数量:7 英寸、4 英里、13 天等。Groovy 允许您通过开放类 直接添加对数量的支持。开源类允许您重新打开现存类并通过在类中添加、删除或修改方法对其进行修改 — 一个强大但危险的机制。幸运的是,这有安全的方法来实现这一任务。Groovy 支持两种不同的开放类语法:categories 和 ExpandoMetaClass。通过 categories 开放类categories 的概念是从 Smalltalk 和 Objective-C 语言中借用的。一个 categories 可以使用 use 块指令,围绕代码调用创建一个包装器,含有一个或多个开放类。通过一个示例更好的理解 categories 概念。清单 1 演示了我已经添加到 String 中的新方法 camelize() 的测试,该方法可以将带下划线的字符串转换成驼峰式大小写:清单 1. 测试演示 camelize()方法class TestStringCategory extends GroovyTestCase {
def expected = ["event_map" : "eventMap",
"name" : "name", "test_date" : "testDate",
"test_string_with_lots_of_breaks" : "testStringWithLotsOfBreaks",
"String_that_has_init_cap" : "stringThatHasInitCap" ]
void test_Camelize() {
use (StringCategory) {
expected.each { key, value ->
assertEquals value, key.camelize()
}
}
}
}
在 清单 1 中,我使用原始的和转换后的案例创建了一个 expected 散列值,然后根据映射的迭代包装 StringCategory,希望将每个关键词驼峰化(camelized)。注意在 use 块中,您不需要特别做什么就可以调用类中的新方法。StringCategory 的代码在清单 2 中显示:清单 2. StringCategory 类class StringCategory {
static String camelize(String self) {
def newName = self.split("_").collect() {
it.substring(0, 1).toUpperCase() + it.substring(1, it.length())
}.join()
newName.substring(0, 1).toLowerCase() + newName.substring(1, newName.length())
}
}
categories 是一个常规类,包含静态方法。静态方法必须要有一个参数,这是您将要增加的类型。在 清单 2 中,我声明了一个单独的静态方法,接收 String 参数(通常称为 self,但是您可以随意为其命名),代表我向其中添加方法的类。方法体包含 Groovy 代码,通过下划线将字符串分成带分隔符的几块(这就是 split("_") 方法所做的),然后将字符串收集到一起,在合适的地方使用大写字母将它们拼接起来。最后一行确保返回的第一个字符是小写的。当您使用 StringCategory 时,您必须在 use 块中访问它。在 use 块的圆括号中有多个 categories 类,之间用逗号隔开,这是合法的。这是在 DSL 中使用开放类表示数量的另一个实例,考虑清单 3 中的代码,实现了一个预约日历:清单 3. 一个简单的日历 DSLdef calendar = new AppointmentCalendar()
use (IntegerWithTimeSupport) {
calendar.add new Appointment("Dentist").from(4.pm)
calendar.add new Appointment("Conference call")
.from(5.pm)
.to(6.pm)
.at("555-123-4321")
}
calendar.print()