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

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

Цели:

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

Подходы

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

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 объекты.Конструктор Query принимает произвольное количество условий и возвращает результат.

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

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

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

UNION

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

Образец строителя:

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"));

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

Подводя итог

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

Ответы [ 2 ]

28 голосов
/ 30 марта 2012

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

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

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

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

  1. is, has, capableOf и т. Д. Являются фабричными методами предикатов. Статические методы - ваш лучший выбор в Java, потому что вы, вероятно, захотите передавать предикаты различным другим DSL-методам вашего API. Я не вижу никаких проблем с интеграцией IDE, автозаполнением или документацией, если вы поместите их в один класс фабрики. В частности, в Eclipse есть для этого приятные функции. Вы можете поместить com.example.Factory.* в «избранное», что приводит к тому, что все методы доступны везде из выпадающего списка автозаполнения (что снова является хорошей точкой доступа для Javadocs). Кроме того, ваш пользователь может просто статически импортировать все методы из Фабрики, где бы они ни понадобились, что дает тот же результат.
  2. and, or, not должны быть методами типа предиката (not также может быть центральным статическим методом). Это приводит к инфиксной записи для логических комбинаций, которая многими разработчиками считается более интуитивной, чем в 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);
    }
    
  3. Для профсоюзов у ​​вас есть несколько вариантов. Некоторые примеры (которые вы также можете объединить):

    // Prefix notation
    public class Factory {
      public static Query union(Query... queries);
    }
    
    // Infix notation
    public interface Query {
      Query union(Query... queries);
    }
    
  4. И последнее, но не менее важное: если вы хотите избежать ключевого слова 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);
    
    }
    

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

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

UNION

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

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

1 голос
/ 30 марта 2012

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


Хорошая статья Дж. Блоха о создании и уничтожении объектов Java , которая может быть вам интересна.

...