【问题标题】:Custom Java query class (DSL): Builder pattern, static imports or something else for complex queries?自定义 Java 查询类 (DSL):构建器模式、静态导入或其他复杂查询?
【发布时间】:2012-03-30 11:34:43
【问题描述】:

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

目标是:

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

方法

目前我能想到两种选择。

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

And/Or/Not 查询

像下面这样更复杂的查询很难表述:

名叫 [michael OR dennis] 的高个子篮球运动员

联合

弯曲有光泽的银勺

建造者模式:

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

总结

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

【问题讨论】:

  • 如果它是一个数据库查询,您的代码看起来与这个库非常相似...jooq.org。你已经考虑过使用像hibernate这样的ORM
  • 不,它与数据库无关。它是关于任何网页上元素的查询。
  • 网页!? jquery.com 将是我的第一个停靠港
  • jOOQ 看起来像是两种选择的组合,即构建器模式中的静态导入。有趣
  • @NimChimpsky:是的,我在核心中使用 jQuery,但这通常是一个具有更多功能的 Java 包装器。

标签: java design-patterns dsl builder static-import


【解决方案1】:

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

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

  1. 了解您的语言应该是什么样子。不要马上考虑 Java(“内部”DSL)。用你自己的语言(“外部”DSL)来思考。在这一点上,您将在 Java 中实现它这一事实并不重要。也许您甚至会在 XML 中实现它,或者您会为它编写自己的解析器/编译器。在用 Java 实现之前先考虑您的语言规范,这将使您的 DSL 更具表现力、更直观和更可扩展。
  2. 一旦您确定了语言的一般语法和语义,请尝试绘制您的语言的BNF notation。一开始你不必过于精确,但这会给它一些正式的方面。 Railroad diagrams 是一个非常好的工具。你会意识到哪些组合是可能的,哪些是不可能的。此外,这是创建整体语言文档的好方法,因为单一方法的 Javadocs 对您的新手用户没有太大帮助。
  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.* 放到您的“收藏夹”中,这会导致自动完成下拉列表中的所有方法都可用(这也是Javadocs 的一个很好的访问点)。或者,您的用户可以在需要时从工厂静态导入所有方法,结果相同。
  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,也请从工厂构造查询(您的 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);
    
    }
    

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

名叫 [michael OR dennis] 的高个子篮球运动员

联合

弯曲有光泽的银勺

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

要获得更多灵感,请查看jOOQjRTF,它在将 Java 中的 RTF(“外部”DSL)建模为“内部”DSL 方面也做得非常出色

【讨论】:

  • 奇妙的回答,非常感谢。我会听从你的建议,希望很快能得到一些结果。
  • @Alp:当然。随时在jooq user groupabove blog post 上讨论更多细节。也许我也可以从您的用例中学到一些东西
  • 事实证明,使用任意嵌套的 AND/OR/NOT 谓词构建 JQuery 命令是一个真正的脑筋急转弯。
  • @Alp:你面临什么问题?
  • 我需要一些时间来制定问题描述。很难对适用于任何布尔谓词组合的通用方法进行建模,因为 JQuery 不是为复杂查询而设计的。
【解决方案2】:

使用静态导入,您必须使用伸缩模式才能使用不同的构造函数创建查询。伸缩构造函数模式有效,但很难编写 当有很多参数时,客户端代码更难阅读。即使您使用 builder 的示例看起来也比使用静态导入更清晰。因此,在您的情况下,构建器似乎是更好的解决方案。


J.Bloch 有一篇关于Creating and Destroying Java Objects 的好文章,您可能会感兴趣。

【讨论】:

  • 感谢您的想法和链接。由于条件类型非常多,伸缩模式确实不是一种选择。你真的认为我的构建器示例比静态导入更清晰吗?如果您能详细说明这一点,我会很高兴。
猜你喜欢
  • 2017-09-07
  • 2022-01-26
  • 1970-01-01
  • 2012-08-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多