我将为我的应用程序创建自定义的流畅API。我决定检查现有库的代码,并在至少2个使用流畅api的项目中发现了一些怪物。
我看到有很多类和接口,只有通用类型的数量不同。
例如:在jOOQ库中DerivedColumnList22
在RxJava中:Action9
像这样的怪物的目的是什么?仅适用于性能案例吗?或者是流行api的一些共性和方式?
当你看到像这样的怪物时,用流畅的api实现自己的DSL看起来很可怕。
答案 0 :(得分:7)
既然你提到了jOOQ library,我会在这里给出一个权威的答案,作为jOOQ的作者,写了这篇关于设计流畅的API(或者更确切地说:用Java设计内部领域特定语言)的帖子: https://blog.jooq.org/2012/01/05/the-java-fluent-api-designer-crash-course
您发现的是API设计人员使用元组的共同愿望。一些语言内置了对结构元组类型的支持,即可以以临时方式创建的元组类型,而不是事先声明它们并给它们命名。后者称为名义打字。 More details about nominal/structural typing here
SQL是允许创建特殊元组类型的语言的一个很好的例子:
SELECT first_name, last_name, age
FROM people
上面的查询创建了一个3级元组的表,类型为(string, string, number)
。 JavaScript是另一个例子,我可以快速生成一个元组:
var x = {
firstName: "Lukas",
lastName: "Eder",
age: undefined
};
还有其他语言在某种程度上具有元组支持。理想情况下,元组允许按名称和索引访问各个属性。有时,只能通过名称进行访问。在其他情况下,它仅由索引给出。但从概念上讲,它总是一样的。
DerivedColumnList22
和Action9
遗憾的是,Java语言没有这样的工具来创建特殊的结构元组类型。 (几乎)Java中的所有内容都需要名义上输入。甚至需要将“匿名函数”(lambda表达式)分配给标称SAM类型:
Runnable r = () -> { doSomething(); }
// ^^^^^^^^^^^^^^^^^^^^^^^^ --- Syntactically looks like a structural type
// ^^^^^^^^^^ ------------------------------ But it's really a nominal type
因此,如果API希望为其用户提供支持实际结构元组类型的假象,则必须提前为每个支持的类型提供名称。例如,Action9
:
Action9<T1, T2, T3, T4, T5, T6, T7, T8, T9> action =
(a, b, c, d, e, f, g, h, i) -> { doSomething(); }
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^ ------------- Look ma! Almost a structural tuple type!
所以,这种技术主要是由像RxJava这样的库实现的(它鼓励结构元组类型真正发挥作用的函数式编程)或者jOOQ(它鼓励结构元组类型也真正发光的SQL)。
其他库/ API包括:
Tuple1
- Tuple22
Tuple1
- Tuple8
这并不意味着您需要在自己的DSL中使用这些类型。从简单开始。最终,您可能觉得需要添加它们。
答案 1 :(得分:2)
在RxJava中,那些编号的接口为需要同时使用2-9个源/值的用户提供了lambda便利。 RxJava未正确使用Action9
,但其Func9
和combineLatest
的姐妹zip
可用作参数。根据Rx.NET的经验,我们认为人们不太可能将联合/ zip用于9个以上的直接来源,而且Func10
不会在列出它们时避免成员排序问题(即Func1
,Func10
,Func2
)。
对于超过9个来源,FuncN<R>
被定义为具有单个方法R call(Object... args)
的功能接口,您需要手动提取元素并将其转换回各自的类型:
Observable.zip(Arrays.asList(ints, strings, objects, t4, t5, t6, ...), a -> {
int p1 = (Integer)a[0];
String p2 = (String)a[1];
//...
})
在幕后,将常规Func2
- Func9
are wrapped加入到FuncN
的combineLatest / zip中,这样这些运营商每个都可以拥有任意数量的源代码,而不是9略有不同。然后包装器只是解压缩它所调用的数组。