В предыдущем посте Маскировка: Скрытие реализации API конструктора мы разделили три основные задачи нашей библиотеки DSL — определение, данные и процесс экспорта. В этой части мы помогаем разработчикам, использующим наш DSL, извлекать часть определений DSL в методы.
Использование DSL имеет свои преимущества, но также и некоторые недостатки. Один из них заключается в том, что компоновщики де-факто являются данными в виде кода, поэтому, например, для представления большой диаграммы нам потребуется написать много кода. В таких ситуациях очень полезно, если вы можете сохранить код СУХИМ и не повторяться.
Одним из популярных рефакторингов для удаления дубликатов является извлечение методов. Внутри вложенных сборщиков это может быть проблемой, так как нам нужен доступ к настраиваемому объекту. Решение на самом деле довольно простое, мы можем явно передать объект определения во вложенное замыкание:
@CompileStatic private static Diagram buildDiagramDiagramUsingHelperMethods() { Diagram.build { DiagramDefinition diagram -> note 'YUML Diagram Components' buildDiagramRelationships(diagram) buildRelationshipRelationship(diagram) } } @CompileStatic private static DiagramContentDefinition buildDiagramRelationships(DiagramDefinition diagram) { diagram.with { type 'Diagram' has one to many type 'Type' type 'Diagram' has zero to many type 'Note' type 'Diagram' has zero to many type 'Relationship' } } @CompileStatic private static DiagramContentDefinition buildRelationshipRelationship(DiagramDefinition diagram) { diagram.with { type 'Relationship' has one type 'Type' called 'source' type 'Relationship' has one type 'Type' called 'destination' type 'Relationship' owns one type 'RelationshipType' } }
В этом тривиальном примере вы можете видеть, что объект DiagramDefinition
передается в качестве первого необязательного аргумента закрытия построителя, поэтому мы можем использовать его в отдельном методе. И мы можем использовать старый добрый метод with
, чтобы продолжить использовать DSL внутри самого метода.
Нам нужно сделать небольшое обновление в интерфейсах, чтобы сообщить компилятору Groovy, каковы ожидаемые параметры закрытия. Вот пример метода type
в DiagramDefinition
:
TypeDefinition type( String name, @DelegatesTo( value = TypeDefinition.class, strategy = Closure.DELEGATE_FIRST ) @ClosureParams( value = SimpleType.class, options = "cz.orany.yuml.model.dsl.TypeDefinition" ) Closure<? extends DiagramContentDefinition> builder );
ClosureParams
аннотация помогает статическому компилятору определить типы параметров замыкания. Хотя это бесполезно в нашей ситуации, вы также можете определить несколько ожидаемых параметров. Поскольку мы по-прежнему используем метод with
внутри, нам не нужны дальнейшие изменения в нашем коде, так как этот метод уже вызывает замыкание с объектом self в качестве единственного параметра.
Вы также можете заметить, что мы добавили возвращаемый тип самого замыкания, который выступает в качестве мягкой защиты, чтобы предотвратить получение полуфабриката свободного оператора, такого как следующий:
type 'Order', { has one }
В примере has one
вернет InheritanceBuilder
, который не реализует интерфейс DiagramContentDefinition
, и во время статической компиляции возникнет ошибка компиляции.
Как я уже писал, эта защита мягкая, так как вы все еще можете написать следующий фрагмент кода:
type 'Order', { has one has many type 'Line' }
Но, тем не менее, это дает разработчикам лучший намек на ожидаемое содержимое замыкания, чем просто оставить параметр типа замыкания неопределенным.
Код доступен на GitHub под тегом 05-dry
:
git clone https://github.com/musketyr/yuml-dsl-builder.git cd yuml-dsl-builder git checkout 05-dry
В следующей части Ожидания: Важность правильной работы с владельцем замыканий мы рассмотрим одну из наиболее распространенных проблем билдера DSL — неверные ожидания относительно захваченной области замыканий.
Содержание
- Концепция: Основная концепция строителей
- Суть: Основы закрытия
- Помощь: Использование аннотаций для статической компиляции
- Маскировка: Скрытие реализации API конструктора
- Иссушение: Сохранение кода СУХИМ
- Ожидания: Важность правильного обращения с владельцем замыканий
- Расширение: Проектирование билдера DSL для расширения
- Отставка: Переписывание Groovy DSL Builder на Java
- Навигация: Использование аннотаций для именованных параметров
- Заключение: Контрольный список для разработчиков Groovy DSL