最近研究了一个类似Groovy的小型DSL工具,并且挖掘了一下griffon和swing以及其他材料,我现在以下列结尾,作为另一个建议提交给答案。 任何意见/建议将不胜感激。
IMHO域模型(一个模型类),并且支持类之间的明确分离(建筑物,聚集,上下文共享,和过滤部分的模式,至少一个生成器类)是关键要素之一 有一个简单而有效的模型。这是groovy swing和griffon中使用的模式,显示出非常灵活。
它允许构造与各{},与{}和收集操作这是干净的,足够的理解, 和大多没有链接操作(即在结尾处添加一个恼人的回报这个每个操作)。
像这里例如(末尾的源代码,MSys的是门面至底层系统):
MSys.with {
model.each {
it.sayHello
it.sayGoodBye
it.setAddress randomAddress(it.name);
it.sendEmail
}
println " Hello counts"
model.each {
def key = it.name
def value = MSys.context[key]
println "Counter for ${key} = ${value}"
}
}
或
MSys.model.each {
it.with {
sayHello
sayGoodBye
setAddress randomAddress(name) ;
sendEmail
}
}
或
MSys.eachModelDo {
sayHello
sayGoodBye
setAddress randomAddress(it.name);
sendEmail
}
或... (很多可能性)
它也允许有一些共享的情况下很容易(在本例中,方面是所有的模型元素,几乎什么事情都可能放,连接信息,用户偏好,缓存等共享的地图), 并通过将其放在另一个班级/脚本中来隐藏所有的锅炉板。
的职责是:
这种分离对于一方面处理设置操作和另一方面处理实体(模型)操作很重要。 除了一个建设者,从用户POV它是一个门面
并从开发人员POV BTW它应该是一个门面几个类,包括建设者 - 但更好的开始简单。
下一个步骤将是FactoryBuilderSupport在这个生成器,以便从Groovy的DSL建设者设施整合中受益,但是这一个大步超越我不舒服与尚未......(WIP现在)
很少Groovy的重述,你肯定已经知道了,但问题的一部分,如果你想有一个光语法:
- 在Groovy ,,;和()是可选的,除非有冲突。
即使没有直接的冲突,代表团策略并不总是直接和 运行时错误并不总是很清楚(见下文关闭文档链接)
- 具有公共getter方法性能,允许像访问方法中的用途的性质没有(), 的获取和设置的前缀可推断
即具有类似方法
public getSendEmail() {
println "email for ${name}"
}
,你可以使用语法:
myObject sendEmail
Groovy的假定属性的getter调用和做休息。 这有助于在dsl语法中删除()。
当您使用关闭,您使用它引用您在关闭工作的对象 (即消息的接收者)。
实施例:
[1, 2, 3].each { println it }
是确定
如果haved使用
[1, 2, 3].each { println this }
你将不得不打印到外部对象的引用,在常规控制台控制台
(其中IIRC是你在第一篇文章中遇到的问题之一)
这在Groovy Closures DOC被很好的解释:
(摘录:)
- 此:其中封闭件所限定的封闭类
- 所有者:包围物体,其中关闭被定义,类或关闭
- 委托人:第三方对象w此方法的调用或属性每当消息的接收者没有定义
的消息代表团策略(同一文件中解释)的解决并不总是直接的,这是你真正的问题,我认为。
也就是说,DSL中的另一个关键点是用户POV的一致性。 这意味着数据访问模式的一致性,这里标准的Groovy集合(列表,地图每个{}等)可以帮助很多。 加上它可以是一个巨大的优势,为电力用户。
MSys.eachModelDo {
sayHello
sayGoodBye
setAddress randomAddress(it.name);
sendEmail
}
NB:eachModelDo是很简单的,但有点棘手调试
在一个点上它“
语法糖的方法,如eachModelDo可以很容易地在建筑工地/门面类完成工作“好,没有访问正确的变量:(
我有这样的感觉,有什么不对(?)或至少应该改善(评论欢迎)
/**
* syntactic sugar
* direct access without 'it' (optional)
*/
public SpecBuilder eachModelDo(closure) {
model.each {
closure.delegate = it;
closure(it)
}
}
贝娄是一个小测试,我做了,你可以剪切和在常规控制台
只有部分可见的用户应该是方法演示粘贴的源代码。运行() 所有其他的东西应该被隐藏
任何意见,欢迎
/**
* The builder build Specs and defines utility methods, filters
* It shares its context with all elements in the domain
*/
class SpecBuilder {
/** the context will be shared with all domain model objects */
private context
/** model = all the domain model objects */
private model
/** getters/setters */
public getModel() { return model }
public getContext() {
return context
}
/** constructors and helpers */
public SpecBuilder(aContext) {
context = aContext
}
/** Default constructor forbidden */
private SpecBuilder() {}
public from(aList, closure) {
from(aList);
model.each { closure(it) }
return this
}
public from(aList) {
model = aList.collect { new SpecModel(it, context) }
return this
}
/* TODO filters etc */
/** stats: print counters */
public stats() {
println " Hello counts"
model.each {
def key = it.name
def value = this.context[key]
println "Counter for ${key} = ${value}"
}
}
/**
* syntactic sugar
* direct access without 'it' (optional)
*/
public SpecBuilder eachModelDo(closure) {
model.each {
closure.delegate = it;
closure(it)
}
}
}
/**
* The Spec Domain Model
*/
class SpecModel {
/** the shared context */
private context;
/** other properties */
private name;
public address;
/** getters and setters */
public getName() { return name }
public void setAddress(a) { address = a }
public getAddress() { return address }
public sayHello() { return getSayHello }
public sayGoodBye() { return getSayGoodBye }
public sendEmail() { return getSendEmail }
/** constructors */
public SpecModel(aName, aContext) {
name = aName
context = aContext
}
/** Default constructor forbidden */
private SpecModel() {}
/** method used like properties, without 'get' and()*/
public getSayHello() {
println "(!) hello ${name}"
context[name] = context.get(name,0) +1;
}
public getSayGoodBye() {
println "goodBye ${name} !"
}
public getSendEmail() {
println "email for ${name}"
if (address)
println "Address ${address}"
}
public getPrintContext() {
println context
}
/**
* Returns info to caller
*/
public gatherInfo() {
"info for ${name} : ${new java.util.Random().nextInt(50000)}"
}
}
class Demo {
// several Groots here to test uniques ...
def customers = ['Groot', 'Gamora', 'Groot', 'Groot', 'Groot', 'Star-Lord']
/**
* Utility function who generates a random address
* @param name will prefix the address
* @return the address
*/
public randomAddress(def name) {
// good places ... :)
def places = [
"Grande Rue",
"Cours Emile Zola",
"Place Antonin Poncet",
"Rue de la République",
"Boulevard de la Croix Rousse",
"Place Bellecour"
]
def random = new java.util.Random();
return new StringBuilder().append(name).append(" ... ")
// why not 42?
.append(random.nextInt(155)).append(" ")
.append(places[random.nextInt(places.size())])
.toString();
}
/**
* ======================== Main user program =========================
*/
def run() {
/** the shared context */
def context = [:].asSynchronized() // In case of multi threading access
/** The whole system From a user POV : a big façade */
def MSys = new SpecBuilder(context).from(customers) ;
println "*** 1 ==================== "
/** First form */
MSys.model.each {
it.with {
sayHello
sayGoodBye
setAddress randomAddress(name) ;
sendEmail
}
}
/** other forms
* MSys.with{ is handy
* one could write MSys... on each line
*/
MSys.with {
println "*** 2 ==================== "
model.each { it.sayHello };
println "*** 3 ==================== "
model.with { println " a Model entry = ${it.name} + ${it.address}" }
println "*** 4 ==================== "
/** false not to mutate the model !!! */
model.unique(false, { a, b -> a.name <=> b.name }).each { it.sayHello }
println "*** 5 ==================== "
context['aKey'] = 42
// verify that each entity has the same context
model.with { println " a shared context for ${it.name} : " + it.context }
println "*** Stats ================ "
/** stats on the shared context */
stats()
}
println "*** 6 Info to process ======== "
/** Gather info to process (addresses)*/
def data = MSys.model.inject([:]) { result, entity ->
result[entity.name] = entity.address
result
}
println data
MSys.with {
println "*** 7 ==================== "
model.each {
it.sayHello
it.sayGoodBye
it.setAddress randomAddress(it.name);
it.sendEmail
}
println "*** 8 ==================== "
println " Hello counts"
model.each {
def key = it.name
def value = MSys.context[key]
println "Counter for ${key} = ${value}"
}
}
println "*** 9 ==================== "
MSys.eachModelDo {
sayHello
sayGoodBye
setAddress randomAddress(it.name);
sendEmail
}
}
}
new Demo().run()
/* end of script */
您好,感谢您的回复。第一种解决方案就是我现在正在做的事情,但试图避免,我使用'close.call(“Toni”)'并将其引用为'TestingSpec.newInstance(it)'。第二种解决方案我没有考虑它,恐怕会更复杂,因为在我的“真实”情况下,Myscript和TestingSpec都是一样的。这个想法实际上是将一个参数从外部的TestingSpec传递给内部(在闭包中)TestingSpec。我现在要去尝试。 – amsmota
@amsmota将“TestingSpec”和“MyScript”作为同一个类对于第二种解决方案来说并不是问题,我写了第三个解决方案,其特点是具有相同的类和闭包委托。另外,考虑在你以前提出的问题中接受一些答案。 – Will
嗨@WillP,再次感谢您的建议。恐怕我没有正确解释自己,主要是因为我是Groovy的新手。所以我不应该像使用MyScript一样使用“script”这个词,因为这是从Java调用而不是groovy脚本的类。所以(我后来才明白这一点)我不能使用绑定选项。然而,你的建议1和3使我提出了一个并不理想的解决方案,但它的“优雅”足以让我的DSL的潜在用户“容易”理解,我将把它作为我的问题的答案。 – amsmota