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

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

цели:

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

подходы

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

1. Шаблон строителя

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

методы is(), capableOf() и name() возвращает ссылку на тег

2 ответов


вы собираетесь реализовать доменный язык (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) из Фабрика:

    // 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


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


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