java - 自定义 Java 查询类 (DSL) : Builder pattern, 静态导入或其他用于复杂查询的东西?

标签 java design-patterns dsl builder static-import

我正在创建一个自定义查询类,但我不确定最优雅的编码方式。

目标是:

  • 易于使用
  • 可扩展性
  • 灵活,可以制定复杂的查询

方法

目前我能想到两种选择。

1。 builder 模式

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

is()capableOf()name() 方法返回对查询对象。 build() 将返回一个 Result 对象。

2。静态导入

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

方法is()capableOf()name() 是静态导入并返回Condition对象。 Query 构造函数采用任意数量的条件并返回结果。

And/Or/Not 查询

像下面这样更复杂的查询是复杂的:

tall basketball player named [michael OR dennis]

UNION

silver spoon which is bent and shiny

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

对我来说看起来更好,但我不太喜欢使用静态导入。它们在 ide 集成、自动完成和文档方面很困难。

总结

我正在寻找有效的解决方案,因此我愿意接受任何类型的建议。我不限于我提出的两种选择,如果您告诉我还有其他可能性,我会很高兴。如果您需要更多信息,请通知我。

最佳答案

您即将实现 domain specific language (DSL) 在 Java 中。有些人会将您的 DSL 称为“内部”DSL,因为您想为它使用标准的 Java 结构,而不是“外部”DSL,后者功能更强大(SQL、XML、任何类型的协议(protocol)),但具有使用字符串连接原始构造。

我司维护jOOQ ,它将 SQL 建模为 Java 中的“内部”DSL(其中一条评论中也提到了这一点)。我对您的建议是按照以下步骤操作:

  1. 了解您的语言应该是什么样子。不要马上考虑 Java(“内部”DSL)。考虑您自己的语言(“外部”DSL)。在这一点上,您将用 Java 实现它这一事实应该不重要。也许您甚至会用 XML 实现它,或者您会为它编写自己的解析器/编译器。首先考虑您的语言规范,然后再用 Java 实现它将使您的 DSL 更具表现力、更直观和更可扩展。
  2. 一旦你确定了你语言的一般语法和语义,试着画一个 BNF notation你的语言。一开始你不必过于精确,但这会给它一些正式的方面。 Railroad diagrams是一个非常好的工具。您将意识到哪些组合是可能的,哪些不是。此外,这是创建整体语言文档的好方法,因为单一方法的 Javadoc 对您的新手用户帮助不大。
  3. 当您拥有正式语法时,请遵循我们在博客中提到的规则:http://blog.jooq.org/2012/01/05/the-java-fluent-api-designer-crash-course .事实证明,这些规则在设计 jOOQ API 时非常有用,据我们的用户报告,这些规则非常直观(如果他们已经了解 SQL)。

我个人给你的建议是:

  1. ishascapableOf 等都是谓词工厂方法。静态方法是您在 Java 中的最佳选择,因为您可能希望能够将谓词传递给 API 的各种其他 DSL 方法。我没有发现 IDE 集成、自动完成或文档有任何问题,只要您将它们都放在同一个工厂类中。具体来说,Eclipse 对此有很好的特性。您可以将 com.example.Factory.* 放入您的“Collection 夹”,这会导致自动完成下拉列表中的所有方法随处可用(这又是 Javadocs 的一个很好的访问点)。或者,您的用户可以在需要的任何地方静态导入工厂中的所有方法,这会产生相同的结果。
  2. and, or, not 应该是谓词类型的方法(not 也可以是一个中央静态方法)。这导致 boolean 组合的中缀表示法,许多开发人员认为它比 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);
    
    }
    

通过这些示例,您可以构造查询(您的示例):

tall basketball player named [michael OR dennis]

UNION

silver spoon which is bent and shiny

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 ,它在将 Java 中的 RTF(“外部”DSL)建模为“内部”DSL 方面也做得非常出色

关于java - 自定义 Java 查询类 (DSL) : Builder pattern, 静态导入或其他用于复杂查询的东西?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/9942155/

相关文章:

javascript - 使用 iife 模式时从另一个 js 文件调用函数?

c++ - 生成具有不同 Ctor 参数的对象实例化的模式

dsl - 使用 XmlCompiler 在运行时定义的条件变量

java - 发现以元素 'simple' 开头的无效内容。 Camel 问题

java - 使用 Item 类的 ArrayList 的销售点应用程序。如何从列表中返回孤立/特定的值?

java - 使用 JButton 问题打开一个新的 JFrame

java - 修改 katharsis json 响应

java - 在简单工厂设计模式(Head-First)中传递工厂对象的目的?

java - 从 PHP/Java/Python Web 应用程序调用 shell 命令(包括那些需要 root 权限的命令)的正确方法是什么?

visual-studio - 使用Microsoft Visual Studio开发文本特定领域语言(DSL)