Подтвердить что ты не робот

Пользовательский класс запросов Java (DSL): шаблон Builder, статический импорт или что-то еще для сложных запросов?

Я создаю пользовательский класс запросов, и я не уверен в самом элегантном способе его кодирования.

Цели:

  • Простота использования
  • расширяемость
  • Гибкий, чтобы можно было сформулировать сложные запросы

Подходы

В настоящее время я могу думать о двух альтернативах.

1. Шаблон Builder

Result r = new Query().is("tall").capableOf("basketball").name("michael").build();

Методы is(), capableOf() и name() возвращают самооценку объекту Query. build() вернет объект Result.

2. Статический импорт

Result r = new Query(is("tall"), capableOf("basketball"), name("michael"));

Методы is(), capableOf() и name() являются статическими и возвращают объекты Condition. Конструктор запросов принимает произвольное количество условий и возвращает результат.

И/Или/Не запросы

Более сложные запросы, подобные следующим, сложны для формулировки:

высокий баскетболист по имени [майкл ИЛИ Деннис]

СОЕДИНЕНИЕ

серебряная ложка, изогнутая и блестящая

Рисунок Builder:

Result r = new Query().is("tall").capableOf("basketball").or(new Query().name("michael"), new Query().name("dennis")).
    union(
        new Query().color("silver").a("spoon").is("bent").is("shiny")
    ).
    build();

Трудно писать и читать. Кроме того, мне не нравится многократное использование new.

Статический импорт:

Result r = new Query(is("tall"), capableOf("basketball"), or(name("michael"), name("dennis"))).
    union(color("silver"), a("spoon"), is("bent"), is("shiny"));

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

Сумма

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

4b9b3361

Ответ 1

Вы собираетесь внедрить в Java язык, специфичный для домена (DSL). Некоторые будут ссылаться на ваш DSL как на "внутренний" DSL, потому что вы хотите использовать для него стандартные Java-конструкции, а не "внешние" DSL файлы, которые намного мощнее (SQL, XML, любой тип протокола), но имеют который будет построен примитивно с использованием конкатенации строк.

Наша компания поддерживает jOOQ, которая моделирует SQL как "внутренний" DSL в Java (об этом также упоминалось в одном из комментариев), Моя рекомендация для вас заключается в следующем:

  • Узнайте, как должен выглядеть ваш язык. Не думайте в терминах Java ( "внутренний" DSL) сразу. Подумайте о своем собственном языке ( "внешний" DSL). Тот факт, что вы реализуете его на Java, не должен быть важен в этот момент. Возможно, вы даже реализуете его в XML, или вы напишете для него свой собственный синтаксический анализатор/компилятор. Сначала подумайте о своей спецификации языка, прежде чем внедрять ее в Java, сделает ваш DSL более выразительным, более интуитивно понятным и более расширяемым.
  • Как только вы решите использовать общий синтаксис и семантику своего языка, попробуйте использовать нотацию BNF на вашем языке. Вначале вам не обязательно быть слишком точным, но это придаст ему некоторые формальные аспекты. Железнодорожные диаграммы - очень хороший инструмент для этого. Вы узнаете, какие комбинации возможны, а какие нет. Кроме того, это хороший способ создать общую документацию по языку, поскольку однопользовательский Javadocs не будет очень полезен для ваших новичков.
  • Когда у вас есть формальный синтаксис, следуйте правилам, которые мы упоминали в нашем блоге: http://blog.jooq.org/2012/01/05/the-java-fluent-api-designer-crash-course. Эти правила оказались очень полезными при разработке jOOQ API, о котором наши пользователи сообщили очень интуитивно (если они уже знают SQL, то есть).

Моя личная рекомендация для вас такова:

  • is, has, capableOf и т.д. являются предикатами factory. Статические методы - ваш лучший выбор на Java, потому что вы, вероятно, захотите передать предикаты другим методам DSL вашего API. Я не вижу никаких проблем с интеграцией IDE, автозаполнением или документацией, если вы поместите их в один класс factory. В частности, Eclipse имеет приятные функции для этого. Вы можете поместить com.example.Factory.* в свои "избранные", что приводит к тому, что все методы доступны повсюду из раскрывающегося списка автоматического завершения (что снова является хорошей точкой доступа для Javadocs). В качестве альтернативы, ваш пользователь может просто статически импортировать все методы из factory там, где он им нужен, что имеет тот же результат.
  • and, or, not должны быть методы для типа предиката (not также может быть центральным статическим методом). Это приводит к нотации infix для булевых комбинаций, что многие разработчики считают более интуитивным, чем то, что сделала JPA/CriteriaQuery:

    public interface Predicate {
    
      // Infix notation (usually a lot more readable than the prefix-notation)
      Predicate and(Predicate... predicate);
      Predicate or(Predicate... predicate);
    
      // Postfix notation
      Predicate not();
    
      // Optionally, for convenience, add these methods:
      Predicate andNot(Predicate... predicate);
      Predicate orNot(Predicate... predicate);
    }
    
    public class Factory {
    
      // Prefix notation
      public static Predicate not(Predicate predicate);
    }
    
  • Для объединений у вас есть несколько вариантов. Некоторые примеры (которые вы также можете комбинировать):

    // Prefix notation
    public class Factory {
      public static Query union(Query... queries);
    }
    
    // Infix notation
    public interface Query {
      Query union(Query... queries);
    }
    
  • И последнее, но не менее важное: если вы хотите избежать ключевого слова new, которое является частью языка Java, а не вашего DSL, также создайте запросы (точки входа вашего DSL) из Factory:

    // Note here! This is your DSL entry point. Choose wisely whether you want
    // this to be a static or instance method.
    // - static: less verbose in client code
    // - instance: can inherit factory state, which is useful for configuration
    public class Factory {
    
      // Varargs implicitly means connecting predicates using Predicate.and()
      public static Query query(Predicate... predicates);
    
    }
    

В этих примерах вы можете построить запросы как таковые (ваш пример):

высокий баскетболист по имени [майкл ИЛИ Деннис]

СОЕДИНЕНИЕ

серебряная ложка, изогнутая и блестящая

Версия Java:

import static com.example.Factory.*;

union(
  query(is("tall"), 
        capableOf("basketball"), 
        name("michael").or(name("dennis"))
  ),
  query(color("silver"),
        a("spoon"),
        is("bent"),
        is("shiny")
  )
);

Для дальнейшего вдохновения посмотрите jOOQ, а также на jRTF, что также отлично работает при моделировании RTF ( "внешний" DSL) в Java как "внутренний" DSL

Ответ 2

При статическом импорте вы должны использовать телескопический шаблон для возможности создания запроса с разными конструкторами. Конструкция конструктора телескопирования работает, но трудно написать клиентский код, когда есть много параметров, и еще труднее его прочитать. Даже ваши примеры с построителем выглядят более понятными, чем при статическом импорте. Так что в вашем случае строитель, кажется, является лучшим решением.


В J.Bloch есть хорошая статья о Создание и уничтожение объектов Java, которые могут быть вам интересны.