15

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

目标是:

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

方法

目前我可以想到两种选择。

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()对象。Query 构造函数接受任意数量的条件并返回结果。name()Condition

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

总结

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

4

2 回答 2

29

您即将在 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 时非常有用,我们的用户报告说它非常直观(如果他们已经知道 SQL,那就是)。

我对你的个人建议是这样的:

  1. is, has,capableOf等是谓词工厂方法。静态方法是 Java 中的最佳选择,因为您可能希望能够将谓词传递给 API 的各种其他 DSL 方法。只要您将它们全部放在同一个工厂类中,我看不出 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);
    
    }
    

使用这些示例,您可以像这样构建查询(您的示例):

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

联盟

弯曲有光泽的银勺

爪哇版:

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方面也做得非常出色

于 2012-03-30T17:40:06.410 回答
1

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


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

于 2012-03-30T12:00:31.677 回答