Groovy DSL 中的强制方法调用顺序

Force method call order in Groovy DSL

我正在 Groovy 中开发一个小型 DSL,我想知道是否有任何方法可以强制方法调用中的顺序。

例如,这将是有效的

SensorDSL.camera {
    take "picture" store_in "path" on {
        success "mySuccessCallback"
        cancel "myCancelCallback"
        error "myErrorCallback"
    }
}

但是在 take 方法之前写 store_in 是不允许的。

这是我当前的代码。

class SensorDSL {   
    def static camera(@DelegatesTo(CameraHandler) Closure closure){
        CameraHandler delegate = new CameraHandler()
        def code = closure.rehydrate(delegate, null, null)
        code.resolveStrategy = Closure.DELEGATE_ONLY
        code()
    }
}

class CameraHandler {
    String mediaType
    String path

    CameraCallbackHandler callbackHandler

    public CameraHandler(){
        callbackHandler = new CameraCallbackHandler()
    }

    CameraHandler take(String mediaType) {
        if (!MediaType.values().collect{it.toString()}.contains(mediaType.toUpperCase())){
            throw new Exception("Only PICTURE or VIDEO can be taken")
        }
        this.mediaType = mediaType
        this
    }

    CameraHandler store_in(String path){
        this.path = path
        this
    }

    void on(@DelegatesTo(CameraCallbackHandler) Closure closure){
        def code = closure.rehydrate(callbackHandler, null, null)
        code.resolveStrategy = Closure.DELEGATE_ONLY
        code.call()
    }

    class CameraCallbackHandler {
        String successCallback
        String errorCallback
        String cancelCallback

        CameraCallbackHandler success(String methodName){
            this.successCallback = methodName
            this
        }

        CameraCallbackHandler cancel(String methodName){
            this.cancelCallback = methodName
            this
        }

        CameraCallbackHandler error(String methodName){
            this.errorCallback = methodName
            this
        }
    }
}

此外,如果有任何方法可以在无需手动检查的情况下进行所需的方法调用,那就太好了。

编辑: 我找到了一种似乎可行的方法。如果方法的 return 是闭包映射,则可以按给定顺序调用方法。例如:

def take(String mediaType){
    [store_in: {path->
        this.path = path
        [on: { Closure closure->
            def code = closure.rehydrate(callbackHandler, null, null)
            code.resolveStrategy = Closure.DELEGATE_ONLY
            code.call()
        }]
    }]
}

但这会出现 IDE 中没有给出代码完成的问题(我使用的是 IntelliJ)。除了保持 IDE?

的代码完成之外,还有其他方法可以强制执行命令吗?

您可以为每个步骤创建自定义 类,而不是闭包映射。例如,take() 方法可以 return 一个仅包含方法 store_in() 的对象,这将return 一个对象,其唯一方法是 on(),依此类推。

我找到了一种似乎有效的方法。如果方法的 return 是闭包映射,则可以按给定顺序调用方法。例如:

def take(String mediaType){
    [store_in: {path->
        this.path = path
        [on: { Closure closure->
            def code = closure.rehydrate(callbackHandler, null, null)
            code.resolveStrategy = Closure.DELEGATE_ONLY
            code.call()
        }]
    }]
}

但这会出现 IDE...

中没有给出代码完成的问题

两个简单的选择是 (a) 维护一个强制调用顺序的状态机,例如,您不能从 store_in 移动到 take,或者 (b) 分解一些 classes.

状态机

可以通过多种方式实现状态机本身并强制执行其上下文。简而言之,简单化是在您的设置器中,您将 (a) 检查当前状态,(b) 确定您进入的状态是否是有效转换,以及 (c) 设置当前 ("next")状态是否允许转换。

在您的示例中,简而言之:

CameraHandler take(String mediaType) {
    // State enforcement elided...

    this.state     = TAKEN
    this.mediaType = mediaType
    this
}

CameraHandler store_in(String path) {
  if (this.state != TAKEN) {
    throw new IllegalStateException("Media type must be specified")
  }

  this.state = STORED
  this.path  = path
  this
}

同样,有很多方法可以实现和强制执行状态机,例如,您可以拥有状态图和可能的后续状态,以及状态处理程序。这可能是一个非常强大的工具。

分解功能

这就是 Emmanuel 的建议。不是 returning CameraHandler(例如,当前实例),而是 return 命令链中允许 "next" 的任何 class。

在你的例子中:

take "picture" store_in "path" on { ... }

take 方法将 return 一个 StorageHandlerstore_in 方法。 StorageHandler 将 return 实现块内的功能。这有一些优点和缺点;你需要携带足够的状态来让它工作——但你需要在某处跟踪那个状态以加强你的语义。将其分解为 classes 可以使它们紧密且易于测试,而神 class 中的状态机可以将注意力从底层功能上转移开。

固有的内部 DSL (iDSL) 限制

内部 DSL (iDSL) 非常酷;我很喜欢它们。但是,它们有一些局限性,而且通常情况下,最小的外部 DSL (eDSL) 允许更大的灵活性和实现选择。

例如,您提到了自动完成:动态语言已经很难自动完成,尽管将内容分解为 class 可以对此有所帮助。它是否 取决于几个因素,例如语言的语法规则是否提供明确的类型解析。 (我最近在 Groovy 工作不多,所以我不确定。)

最小的 eDSL 可以在 IDE 中实现,例如 IntelliJ 的 MPS,并给你完成。有一些基于 Web 的文本编辑器可以帮助 eDSL 突出显示和完成。一些 eDSL 和 iDSL 也可以使用相同的语法。

最终还是要看你的需求了。