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