2015-08-28 1 views
3

Я разрабатываю небольшую DSL в Groovy, и мне было интересно, есть ли способ заставить заказ в вызовах метода.Порядок вызова метода метода в Groovy DSL

Например, это было бы справедливо

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

но писать store_in перед тем take метод не должен быть разрешен.

Вот мой текущий код.

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 
     } 
    } 
} 

Также было бы здорово, если есть способ сделать вызов метода без ручной проверки.

Редактировать: Я нашел способ, который, кажется, работает. Если возврат метода - это карта замыканий, вы можете вызвать методы в данном порядке. Например:

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?

+0

Так как они просто объекты недвижимости, на самом деле, если вы не возвращаете контекстно-зависимые объекты. Например, 'store_in' возвращает экземпляр' PictureTaker' вместо того, чтобы вставлять все в 'CameraHandler'. Я бы поставил вопрос, действительно ли порядок здесь. –

+0

Возможно, это не очень актуально в этом примере, но мне нужно сделать некоторые другие DSL, в которых было бы больше смысла форсировать заказ. – Eylen

+2

Два простых варианта: (a) поддерживать конечный автомат, который обеспечивает порядок вызова, например, вы можете 't перейти от 'store_in' к' take' или (b) разбить некоторые классы. Есть некоторые внутренние ограничения для внутренних DSL; иногда лучше писать минимальный внешний DSL, когда контекст и семантика очень важны. –

ответ

2

Два легкий выбор (а) поддерживать государственную машину, которая претворяет порядок вызова, например, вы не можете перейти из store_in, чтобы взять, или (b) разбить некоторые из классов.

Государственные машины

Есть множество способов государственная машина сама может быть реализована и контекстов насильственными. В двух словах, упрощенном заключается в том, что в ваших сеттерах вы бы (а) изучили текущее состояние, (b) определите, является ли состояние, в которое вы входите, действительным переходом, и (c) установите текущее («следующее») состояние, если переход разрешен.

В вашем примере, скорлупа:

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 
} 

Опять же, есть тонна способов состояния машина может быть реализована и насильственными, например, вы могли бы иметь карту состояний и, возможно, следующие состояния, наряду с государственные обработчики. Это может быть довольно мощный инструмент.

Breaking Up Функциональность

Вот что предложил Эммануэль. Вместо возврата CameraHandler (например,, текущий экземпляр), вы должны вернуть любой класс, который был бы «следующий» в цепочке команд.

В вашем примере:

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

Метод take возвратит StorageHandler с методом store_in. StorageHandler вернет все, что реализует функциональность внутри блока. Это имеет некоторые преимущества и недостатки; вам нужно будет нести достаточное состояние, чтобы заставить его работать –, но вам нужно отслеживать это состояние где-то, чтобы обеспечить соблюдение семантики. Разбивание его на классы позволяет им быть жестким и легко проверяемым, тогда как конечный автомат класса богов может отвлечь внимание от базовых функций.

Врожденный Внутренний DSL (IDSL) Ограничения

Внутренние DSL, (IDSL) являются довольно прохладно; Я наслаждаюсь ими совсем немного. Однако у них есть некоторые ограничения, и часто бывает так, что минимальные внешние DSL (eDSL) обеспечивают большую гибкость и варианты реализации.

Например, вы упомянули автозаполнения: динамические языки уже сложно автозаполнять, хотя нарушение правил в классе может помочь с этим. Является ли это , зависит от нескольких факторов, например, независимо от того, обеспечивают ли синтаксические правила языка однозначное разрешение по типу. (Я не очень много работал в Groovy, поэтому я не уверен.)

Минимальные eDSL могут быть реализованы в IDE, например, в MPS IntelliJ, и дают вам возможность завершить. Существуют текстовые редакторы, которые могут помочь с eDSL с подсветкой и завершением. Некоторые eDSL и iDSL могут использовать один и тот же синтаксис.

В конечном счете это зависит от ваших потребностей.

+0

Фантастическое объяснение @ dave-newton! –

2

Вместо карты закрытий вы можете создавать пользовательские классы для каждого шага. Например, метод take() может вернуть объект, содержащий только метод store_in(), который будет возвращать объект с единственным методом on() и т. Д.

+0

Я думаю, что это решение представило бы слишком много шума, поскольку один класс для каждого метода может стать немного сложным для поддержания, не так ли? – Eylen

+0

Да, но он будет обеспечивать выполнение порядка dsl и поддерживать завершение кода IDE. –

0

Я нашел способ, который, кажется, работает. Если возврат метода - это карта замыканий, вы можете вызвать методы в данном порядке. Например:

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 ...

+0

Внутренние DSL обычно имеют проблемы с завершением кода; потому что они по своей сути динамичны, для синтаксического анализа IDE сложно понять, что может «прийти дальше». У государственной машины будет такая же проблема. Это одна из причин, почему минимальные внешние DSL (например, решение IntelliJ или простые старые редакторы, поддерживающие подсветку синтаксиса или минимальные пользовательские редакторы) иногда могут быть лучшим выбором. –