В предыдущем посте Расширение: Проектирование билдера DSL с учетом расширяемости мы предоставили точку расширения для нашего билдера DSL. В этой части мы собираемся переписать API DSL на Java, чтобы охватить более широкую аудиторию.

Groovy является хорошим гражданином в стране JVM, но, тем не менее, сообщество разработчиков Java во много раз больше, чем сообщество Groovy. К счастью, довольно легко адаптировать построитель Groovy DSL к построителю Java, хотя код Java всегда будет более подробным.

Интерфейс Java Consumer может служить надежной заменой Closure Groovy. В следующем примере показан DSL, переписанный на Java с использованием Consumer lambdas:

Diagram.create(d -> {
    d.note("You can stick notes on diagrams too!", "skyblue");
    d.aggregation("Customer", "Order", r -> {
        r.source("1");
        r.destination("0..*", "orders");
    });
    d.composition("Order", "LineItem", r -> {
        r.source("*");
        r.destination("*");
    });
    d.association("Order", "DeliveryMethod", r -> {
        r.destination("1");
    });
    d.association("Order", "Product", r -> {
        r.source("*");
        r.destination("*");
    });
    d.association("Category", "Product", r -> {
        r.bidirectional(true);
    });
    d.type("National").inherits(from).type("DeliveryMethod");
    d.type("International").inherits(from).type("DeliveryMethod");
})

Интерфейсы Builder теперь имеют методы, подобные следующему:

RelationshipDefinition relationship(
    String source,
    RelationshipType relationshipType,
    String destination,
    Consumer<RelationshipDefinition> additionalProperties
);

Мы по-прежнему хотим поддерживать старый Groovy DSL, который у нас был раньше. Хотя мы можем просто использовать замыкания вместо Consumer или любого другого отдельного типа абстрактного метода (типа SAM), но мы потеряем возможность указывать делегата.

Я считаю хорошей практикой не смешивать Java и Groovy части DSL. Поскольку многие Java-разработчики все еще напуганы добавлением Groovy в путь к классам, в идеале мы должны переписать все части на Java. Затем мы можем предоставить Groovy DSL как модуль расширения в отдельной библиотеке. Чтобы не усложнять пример YUML, модуль расширения остается частью того же проекта.

Мы уже использовали модули расширения для расширения функциональности конструктора. Теперь мы можем использовать его для полного извлечения методов с помощью замыканий. Каждый из методов, использующих Consumer, будет иметь аналог в классе расширения:

public static RelationshipDefinition relationship(
    DiagramDefinition self,
    String source,
    RelationshipType relationshipType,
    String destination,
    @DelegatesTo(
        value = RelationshipDefinition.class,
        strategy = Closure.DELEGATE_FIRST
    )
    @ClosureParams(
        value = SimpleType.class,
        options = "cz.orany.yuml.model.dsl.RelationshipDefinition"
    )
    Closure<? extends DiagramContentDefinition> additionalProperties
) {
    return self.relationship(
        source,
        relationshipType,
        destination,
        ConsumerWithDelegate.create(additionalProperties)
    );
}

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

Вам также необходимо изменить способ введения ключевых слов в DSL. Ранее ключевые слова предоставлялись как статические геттеры на интерфейсах, но это не будет очень практично из кода Java. Самое простое решение — извлечь все ключевые слова в один класс-держатель, который мы можем импортировать как статический импорт.

public class DiagramKeywords {

    public static final From from = From.FROM;
    public static final Integer zero = 0;
    public static final Integer one = 1;
    public static final String many = "*";

}

Код доступен на GitHub под тегом 08-java-dsl:

git clone https://github.com/musketyr/yuml-dsl-builder.git
cd yuml-dsl-builder
git checkout 08-java-dsl

В следующей части Навигация: Использование аннотаций для именованных параметров мы рассмотрим новую функцию в Groovy 2.5, которая представляет собой поддержку проверки типов именованных параметров метода.

Содержание

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