1

我想创建一个语法如下的 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

如何将内部闭包的缺失方法调用限定为我创建的内部对象?

4

3 回答 3

2

简单的答案是将内部闭包设置resolveStrategy为“首先委托”,但是当委托定义 amethodMissing以拦截所有方法调用时这样做会导致无法在闭包外部定义方法并从内部调用它,例如

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(...)and调用将definedMethod1()沿着所有者链向上并MissingMethodException从最外层范围获取 a,然后尝试最外层闭包的委托,最终在Graph.methodMissing. 然后会看到有一个currentObj并将方法调用传回给那个,这反过来又会在适当ObjImpl.definedMethod1的时候结束。ObjImpl.methodMissing

如果您的 DSL 可以嵌套超过两层,那么您将需要保留一堆“当前对象”而不是单个引用,但原理完全相同。

于 2012-08-31T23:45:50.677 回答
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 的工作方式,builder 本身是 DSL 树所有级别的闭包委托。它使用默认的“所有者优先”解析策略调用其所有闭包,这意味着您可以在 DSL 外部定义一个方法并从内部调用它,例如

def calculateSomething() {
  return "something I calculated"
}

Graph.make {
  foo {
    bar(calculateSomething())
    definedMethod1()
  }
}

但同时对 定义的方法的任何调用ObjImpl都将被路由到当前对象(foo本例中的节点)。

于 2012-09-01T12:05:02.917 回答
0

这种方法至少有两个问题:

  1. ObjImpl在与 as 相同的上下文中定义Graph意味着任何missingMethod呼叫都将Graph首先发生
  2. 除非设置了 a ,否则委派似乎在本地发生resolveStrategy,例如:

    closure.resolveStrategy = Closure.DELEGATE_FIRST
    
于 2012-08-31T22:13:55.863 回答