我想用以下语法创建一个DSL:
Graph.make {
foo {
bar()
definedMethod1() // isn't missing!
}
baz()
}
当这个树的处理程序遇到最外面的闭包时,它会创建一个类的实例,它有一些已定义的方法,也有自己的缺失方法处理程序。
我认为这很容易,有一些结构,如:
public class Graph {
def static make(Closure c){
Graph g = new Graph()
c.delegate = g
c()
}
def methodMissing(String name, args){
println "outer " + name
ObjImpl obj = new ObjImpl(type: name)
if(args.length > 0 && args[0] instanceof Closure){
Closure closure = args[0]
closure.delegate = obj
closure()
}
}
class ObjImpl {
String type
def methodMissing(String name, args){
println "inner " + name
}
def definedMethod1(){
println "exec'd known method"
}
}
}
但是,methodMissing处理程序解释了Graph中的整个闭包,而不是将内部闭包委托给ObjImpl,从而产生输出:
outer foo
outer bar
exec'd known method
outer baz
如何将内部闭包的缺失方法调用范围限定为我创建的内部对象?
答案 0 :(得分:2)
简单的答案是将内部闭包的resolveStrategy
设置为“委托第一”,但是当委托定义methodMissing
来拦截所有方法调用时,请执行此操作使得无法在闭包之外定义方法并从内部调用它的效果,例如
def calculateSomething() {
return "something I calculated"
}
Graph.make {
foo {
bar(calculateSomething())
definedMethod1()
}
}
为了允许这种模式,最好将所有闭包保留为默认的“所有者优先”解析策略,但让外部methodMissing
知道何时正在进行内部闭包并返回到那个:
public class Graph {
def static make(Closure c){
Graph g = new Graph()
c.delegate = g
c()
}
private ObjImpl currentObj = null
def methodMissing(String name, args){
if(currentObj) {
// if we are currently processing an inner ObjImpl closure,
// hand off to that
return currentObj.invokeMethod(name, args)
}
println "outer " + name
if(args.length > 0 && args[0] instanceof Closure){
currentObj = new ObjImpl(type: name)
try {
Closure closure = args[0]
closure()
} finally {
currentObj = null
}
}
}
class ObjImpl {
String type
def methodMissing(String name, args){
println "inner " + name
}
def definedMethod1(){
println "exec'd known method"
}
}
}
通过这种方法,在给定上述DSL示例的情况下,calculateSomething()
调用将向所有者链传递并到达调用脚本中定义的方法。 bar(...)
和definedMethod1()
调用将上升到所有者链并从最外层范围获取MissingMethodException
,然后尝试最外层闭包的委托,最后在Graph.methodMissing
。然后会看到有一个currentObj
并将方法调用传回给它,然后根据需要将其传递到ObjImpl.definedMethod1
或ObjImpl.methodMissing
。
如果您的DSL可以嵌套超过两级,那么您需要保留一堆“当前对象”而不是单个引用,但原理完全相同。
答案 1 :(得分:1)
另一种方法可能是使用groovy.util.BuilderSupport
,这是专为像你这样的树构建DSL而设计的:
class Graph {
List children
void addChild(ObjImpl child) { ... }
static Graph make(Closure c) {
return new GraphBuilder().build(c)
}
}
class ObjImpl {
List children
void addChild(ObjImpl child) { ... }
String name
void definedMethod1() { ... }
}
class GraphBuilder extends BuilderSupport {
// the various forms of node builder expression, all of which
// can optionally take a closure (which BuilderSupport handles
// for us).
// foo()
public createNode(name) { doCreate(name, [:], null) }
// foo("someValue")
public createNode(name, value) { doCreate(name, [:], value) }
// foo(colour:'red', shape:'circle' [, "someValue"])
public createNode(name, Map attrs, value = null) {
doCreate(name, attrs, value)
}
private doCreate(name, attrs, value) {
if(!current) {
// root is a Graph
return new Graph()
} else {
// all other levels are ObjImpl, but you could change this
// if you need to, conditioning on current.getClass()
def = new ObjImpl(type:name)
current.addChild(newObj)
// possibly do something with attrs ...
return newObj
}
}
/**
* By default BuilderSupport treats all method calls as node
* builder calls. Here we change this so that if the current node
* has a "real" (i.e. not methodMissing) method that matches
* then we call that instead of building a node.
*/
public Object invokeMethod(String name, Object args) {
if(current?.respondsTo(name, args)) {
return current.invokeMethod(name, args)
} else {
return super.invokeMethod(name, args)
}
}
}
BuilderSupport的工作方式,构建器本身是DSL树的所有级别的闭包委托。它使用默认的“所有者优先”解析策略调用其所有闭包,这意味着您可以在DSL之外定义一个方法并从内部调用它,例如。
def calculateSomething() {
return "something I calculated"
}
Graph.make {
foo {
bar(calculateSomething())
definedMethod1()
}
}
但同时对ObjImpl
定义的方法的任何调用都将路由到当前对象(本例中为foo
节点)。
答案 2 :(得分:0)
这种方法至少存在两个问题:
ObjImpl
相同的上下文中定义Graph
表示任何missingMethod
来电都会先点击Graph
除非设置resolveStrategy
,否则委托似乎在本地发生,例如:
closure.resolveStrategy = Closure.DELEGATE_FIRST