自定义Java查询类(DSL):生成器模式、静态导入或其他用于复杂查询的东西



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

目标是:

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

方法

目前我能想到两种选择。

1.建设者模式

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构造函数接受任意数量的条件并返回结果。

和/或/非查询

更复杂的查询(如以下)很难表述:

名为[迈克尔或丹尼斯]的高个子篮球运动员

联合

银汤匙是弯曲和有光泽的

生成器模式:

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集成、自动完成和文档方面都很困难。

总结

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

您即将在Java中实现一种领域特定语言(DSL)。有些人会把你的DSL称为"内部"DSL,因为你想使用标准的Java构造,而不是"外部"DSL,后者功能更强大(SQL、XML、任何类型的协议),但必须首先使用字符串串联来构造。

我们公司维护jOOQ,它将SQL建模为Java中的"内部"DSL(其中一条评论中也提到了这一点)。我建议你遵循以下步骤:

  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.事实证明,这些规则在设计jOOQ API时非常有用,我们的用户报告说,jOOQ非常直观(如果他们已经知道SQL的话)

我个人给你的建议是:

  1. ishascapableOf等都是谓词工厂方法。在Java中,静态方法是您的最佳选择,因为您可能希望能够将谓词传递给API的各种其他DSL方法。我认为IDE集成、自动完成或文档没有任何问题,只要将它们放在同一个工厂类中即可。特别是Eclipse有很好的特性。您可以将com.example.Factory.*放在您的"收藏夹"中,这将导致自动完成下拉列表中的所有方法都可用(这也是Javadocs的一个很好的访问点)。或者,用户可以在需要的地方静态导入Factory中的所有方法,这样会得到相同的结果
  2. andornot应该是谓词类型上的方法(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的一部分。也可以从Factory构建查询(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,它在将Java中的RTF("外部"DSL)建模为"内部"DSL 方面也做得很好

对于静态导入,您必须使用伸缩模式来创建具有不同构造函数的查询。伸缩构造函数模式有效,但很难编写当有很多参数时,客户端代码更难读取。即使是使用生成器的示例看起来也比使用静态导入更清晰。所以在您的案例中,构建器似乎是更好的解决方案。


J.Bloch有一篇关于创建和销毁Java对象的好文章,这篇文章对您来说可能很有趣。

相关内容

  • 没有找到相关文章

最新更新