我正在创建一个自定义查询类,但我不确定最优雅的编码方式。
目标是:
- 易于使用
- 可扩展性
- 灵活,可以制定复杂的查询
方法
目前我能想到两种选择。
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(其中一条评论中也提到了这一点)。我对您的建议是按照以下步骤操作:
- 了解您的语言应该是什么样子。不要马上考虑 Java(“内部”DSL)。考虑您自己的语言(“外部”DSL)。在这一点上,您将用 Java 实现它这一事实应该不重要。也许您甚至会用 XML 实现它,或者您会为它编写自己的解析器/编译器。首先考虑您的语言规范,然后再用 Java 实现它将使您的 DSL 更具表现力、更直观和更可扩展。
- 一旦你确定了你语言的一般语法和语义,试着画一个 BNF notation你的语言。一开始你不必过于精确,但这会给它一些正式的方面。 Railroad diagrams是一个非常好的工具。您将意识到哪些组合是可能的,哪些不是。此外,这是创建整体语言文档的好方法,因为单一方法的 Javadoc 对您的新手用户帮助不大。
- 当您拥有正式语法时,请遵循我们在博客中提到的规则:http://blog.jooq.org/2012/01/05/the-java-fluent-api-designer-crash-course .事实证明,这些规则在设计 jOOQ API 时非常有用,据我们的用户报告,这些规则非常直观(如果他们已经了解 SQL)。
我个人给你的建议是:
is
、has
、capableOf
等都是谓词工厂方法。静态方法是您在 Java 中的最佳选择,因为您可能希望能够将谓词传递给 API 的各种其他 DSL 方法。我没有发现 IDE 集成、自动完成或文档有任何问题,只要您将它们都放在同一个工厂类中。具体来说,Eclipse 对此有很好的特性。您可以将com.example.Factory.*
放入您的“Collection 夹”,这会导致自动完成下拉列表中的所有方法随处可用(这又是 Javadocs 的一个很好的访问点)。或者,您的用户可以在需要的任何地方静态导入工厂中的所有方法,这会产生相同的结果。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); }
对于工会,您有多种选择。一些示例(您也可以组合):
// Prefix notation public class Factory { public static Query union(Query... queries); } // Infix notation public interface Query { Query union(Query... queries); }
最后但同样重要的是,如果您想避免
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/