В предыдущей части Суть: Основы замыканий мы показали, как использовать встроенные методы Groovy для создания прототипа нашего DSL-строителя. В этом посте мы собираемся реализовать наши собственные обработчики замыканий, которые обеспечивают поддержку статической компиляции.

Почему мы вообще должны беспокоиться о статической компиляции в Groovy? Во-первых, он работает быстрее и дает вам еще один уровень уверенности, но главная причина в том, что простое практическое правило:

Если код можно скомпилировать статически, IDE поймет ваш код и даст вам все ожидаемые подсказки.

Хотя Cédric Champeau уже много лет назад реализовал статическую компиляцию в языке Groovy, многие разработчики до сих пор игнорируют эту функцию.

При разработке построителей DSL в Groovy есть три ключевых момента, которые могут увеличить опыт разработчиков, если наш метод принимает Closure:

Давайте взглянем на другую итерацию нашего YUML DSL:

Diagram.build {
    note('You can stick notes on diagrams too!', 'skyblue')

    aggregation('Customer', 'Order') {
        source '1'
        destination '0..*', 'orders'
    }

    composition('Order', 'LineItem') {
        source '*'
        destination '*'
    }

    association('Order', 'DeliveryMethod') {
        destination '1'
    }

    association('Order', 'Product') {
        source '*'
        destination '*'
    }

    association('Category', 'Product') {
        bidirectional true
    }

    type 'National' inherits from type 'DeliveryMethod'
    type'International' inherits from type 'DeliveryMethod'
}

В последней версии DSL используются две новые функции.

  1. DSL построителя древовидной структуры с использованием замыканий Groovy
  2. Свободный DSL с использованием выражений цепочки команд

Методы note и relationship теперь принимают замыкания в качестве последнего аргумента, что позволяет нам поместить их за скобки вызова метода. Аргументы закрытия помечены аннотацией DelegatesTo, чтобы дать подсказки для статического компилятора. Они по-прежнему используют withmethod внутри:

Relationship relationship(
    String source,
    RelationshipType relationshipType,
    String destination,
    @DelegatesTo(
        value = Relationship, 
        strategy = Closure.DELEGATE_FIRST
    )
    Closure additionalProperties = Closure.IDENTITY
) {
    Relationship relationship = new Relationship(
        type(source), 
        relationshipType, 
        type(destination)
    )
    relationship.with additionalProperties
    this.relationships.add(relationship)
    return relationship
}

Распространенной ошибкой является забывание установки strategy на Closure.DELEGATE_FIRST, так как по умолчанию strategy аннотации DelegatesTo равно Closure.OWNER_FIRST.

Свободная часть DSL использует некоторые вспомогательные объекты, чтобы сохранить ритм метод-параметр-метод-параметр -*. Например, Type теперь предоставляет метод inherits, который возвращает InheritanceBuilder

InheritanceBuilder inherits(From from) {
    return new InheritanceBuilder(diagram, this)
}

Класс InheritanceBuilder довольно прост:

@CompileStatic
class InheritanceBuilder {

    private final Type source
    private final Diagram diagram


    InheritanceBuilder(Diagram diagram, Type destination) {
        this.source = destination
        this.diagram = diagram
    }

    Relationship type(String destination) {
        return diagram.inheritance(source.name, destination)
    }
}

Этим вспомогательным классам всегда нужно сохранять некоторые обратные ссылки, чтобы помочь построить желаемый объект.

Последние части головоломки — это методы класса Diagram, которые возвращают ключевые слова, такие как from:

static From getFrom() {
    return From.FROM
}

Код доступен на GitHub под тегом 02-closures:

git clone https://github.com/musketyr/yuml-dsl-builder.git
cd yuml-dsl-builder
git checkout 02-closures

В следующей части Маскировка: Скрытие реализации API билдера мы собираемся сделать дизайн нашего DSL билдера чище, разделив данные и часть определения библиотеки.

Содержание

  1. Концепция: Основная концепция строителей
  2. Суть: Основы закрытия
  3. Помощь: Использование аннотаций для статической компиляции
  4. Маскировка: Скрытие реализации API конструктора
  5. Иссушение: Сохранение кода СУХИМ
  6. Ожидания: Важность правильного обращения с владельцем замыканий
  7. Расширение: Проектирование билдера DSL для расширения
  8. Отставка: Переписывание Groovy DSL Builder на Java
  9. Навигация: Использование аннотаций для именованных параметров
  10. Заключение: Контрольный список для разработчиков Groovy DSL