Рубрики
Без рубрики

Создание DSL-подобных API в Java (и исправление шаблона Builder)

Сборочная линия для сложных конструкций. Помеченный java, lang.

Во многих случаях Java DSL – это просто способ собрать некоторую сложную конфигурацию, а затем передать построенную структуру внутреннему методу, который будет ее обрабатывать. Например, разработчики SQL-запросов, разработчики HTTP-запросов – все они попадают в эту категорию.

Прежде всего, простой шаблон Builder редко бывает удобен в таких случаях. Он не имеет ограничений на порядок вызовов и может привести к неполной конфигурации или просто к нечитаемому коду.

Решение состоит в том, чтобы разделить Builder на набор интерфейсов, соединяющих друг друга.

Чтобы проиллюстрировать подход, давайте представим, что мы пытаемся написать еще один, очень простой конструктор HTTP-запросов. При описанном подходе это может выглядеть следующим образом:

public interface RequestBuilder {
    static Stage1 get(String uri) {
        return new Builder(Method.GET, uri, list());
    }

    interface Stage1 {
        default Stage2 withoutParameters(Object... parameters) {
            return with();
        }

        Stage2 with(Object... parameters);
    }

    interface Stage2 {
        Request build();
    }

    class Builder implements Stage1, Stage2 {
        private final Method method;
        private final String uri;
        private final List parameters;

        private Builder(final Method method, final String uri, final List parameters) {
            this.method = method;
            this.uri = uri;
            this.parameters = parameters;
        }

        @Override
        public Stage2 with(final Object... parameters) {
            return new Builder(method, uri, list(parameters));
        }

        @Override
        public Request build() {
            return new Request(method, uri, parameters);
        }
    }
}

Строительный код сосредоточен в Builder class. Эта реализация использует неизменяемый экземпляр, но это не является строго необходимым.

Весь класс Builder никогда не предоставляется непосредственно пользовательскому коду. Вместо этого мы возвращаем интерфейс, который ограничивает действия пользователя с помощью() и без параметров() . Эти методы, в свою очередь, возвращают интерфейс следующего этапа построения ( Этап 2 ), который позволяет пользователю завершить построение объекта.

Использование примера кода может выглядеть следующим образом. Обратите внимание, что нет никакого способа не явно указывать параметры (или их отсутствие):

...
RequestBuilder.get("http://somewhere.com/api/users/{param1}")
              .with(userId1)
              .build();
...
RequestBuilder.get("http://somewhere.com/api/users")
              .withoutParameters()
              .build();
...

Пример довольно прост, но на самом деле может быть любое количество промежуточных этапов, и они не обязательно должны фиксировать процесс построения как однонаправленный граф (например, создание циклов для сборки сложного подобъекта также может быть легко выполнено).

Что мы получаем в свою очередь:

  • Нет никакого способа построить неполный объект
  • Пользователь проходит этапы построения объекта с помощью IDE и ограниченного количества вариантов выбора на каждом этапе
  • Все вызовы через код следуют одному и тому же шаблону, что упрощает чтение и просмотр такого кода
  • Весь API, правильно спроектированный и названный, может сформировать довольно удобный в использовании DSL.

Оригинал: “https://dev.to/siy/creating-dsl-like-api-s-in-java-1khc”