【问题标题】:Creating custom fluent API创建自定义流式 API
【发布时间】:2016-09-21 13:55:44
【问题描述】:

我将为我的应用程序创建自定义 fluent API。我决定检查现有库的代码,并在至少 2 个使用 fluent api 的项目中发现了一些怪物。

我看到有许多类和接口,它们的区别仅在于泛型类型的数量。

例如:在jOOQ库中DerivedColumnList22

在 RxJava 中:Action9

这样的怪物的目的是什么?它仅适用于性能案例吗?或者是流利的api有一些共性和方法吗?

当你看到这样的怪物时,在 fluent api 中实现自己的 DSL 看起来很可怕。

【问题讨论】:

    标签: java dsl fluent


    【解决方案1】:

    既然你提到了jOOQ library,我将在这里给出一个权威的答案,作为jOOQ的作者,并写了这篇关于设计流畅API的帖子(或者更确切地说:用Java设计内部特定领域的语言): https://blog.jooq.org/2012/01/05/the-java-fluent-api-designer-crash-course

    SomeType22 只是一个名义元组类型

    您发现的是 API 设计人员使用元组的共同愿望。一些语言内置了对结构化元组类型的支持,即可以以特别方式创建的元组类型,而不是提前声明它们并给它们命名。后者称为名义类型。 More details about nominal/structural typing here.

    支持结构元组类型的语言示例:

    SQL 是允许创建临时元组类型的语言的一个很好的例子:

    SELECT first_name, last_name, age
    FROM people
    

    上述查询创建了一个类型为(string, string, number) 的3 级元组表。 JavaScript 是另一个示例,我可以在其中快速生成这样的元组:

    var x = {
        firstName: "Lukas",
        lastName: "Eder",
        age: undefined
    };
    

    还有其他语言在某种程度上支持元组。理想情况下,元组允许通过名称​​和索引访问单个属性。有时,只能通过名称授予访问权限。在其他情况下,它仅由索引给出。但从概念上讲,它总是一样的。

    DerivedColumnList22Action9 是什么意思

    不幸的是,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 包括:

    这并不意味着您在自己的 DSL 中需要这些类型。开始简单。最终,您可能会觉得需要添加它们。

    【讨论】:

    • 我一直想知道你是如何决定停止的。为什么是 Record22 而不是 Record21Record23 ?这个数字是怎么决定的?根据一些统计数据?
    • @Spotted:基于 Scala,其元组范围从 Tuple1Tuple22。现在为什么 他们 以 22 停止是一个不同的问题:) (注意,在 jOOQ 中,在 22 之后你只是失去了类型安全,但你可以继续使用 Record 永远)
    • 是的,我的问题是:你知道 他们为什么停在 22 吗?更一般地说,在设计此类系统时,您如何决定何时停止?您是从小处着手,当人们开始抱怨时您会增加,还是您尝试收集一些数据来计算一些使用预测并相应地进行设计?
    • 我认为大多数 Scala 核心人员都不知道。可能是一些精心制作的笑话,有点像 42 的一半(因为 42),然后是一个错误。你什么时候停下来?简单的。当你达到“足够好”时。正如你所说,你会等到有人抱怨。在 SQL 中,22 是不够的。在函数式编程中,通常 8 就足够了(参见 .NET's tuples
    【解决方案2】:

    在 RxJava 中,这些编号的接口为需要同时处理 2-9 个源/值的用户提供了 lambda 便利。 Action9 没有被 RxJava 使用,但它的姐妹 Func9 可以作为 combineLatestzip 的参数使用。根据 Rx.NET 的经验,我们认为人们不太可能将 combine/zip 与超过 9 个直接来源一起使用,而且在列出它们时没有Func10 可以避免成员排序问题(即Func1Func10 , Func2)。

    对于超过 9 个来源,FuncN&lt;R&gt; 定义为具有单个方法 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 变成了 combineLatest/zip 的 FuncN,因此这些运算符每个都可以有任意数量的源实现,而不是 9 个略有不同的源实现。然后包装器只是解包它被调用的数组,就像上面一样。

    【讨论】:

      猜你喜欢
      • 2019-10-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-07-16
      • 2015-03-12
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多