【问题标题】:Benefits of @FunctionalInterface in staged builders@FunctionalInterface 在分阶段构建器中的好处
【发布时间】:2019-12-07 21:13:24
【问题描述】:

简介

在搜索网络时,我偶然发现了 Benoit Tellier 的一篇博文,Next level Java 8 staged builders,其中他分享了他针对某些用例的分阶段构建器模式的变体。

我注意到阶段用@FunctionalInterface 注释。这是他帖子中的一个例子(没有技术):

public static class MailboxCreatedBuilder {
    @FunctionalInterface
    public interface RequireUser {
        RequireSessionId user(User user);
    }

    @FunctionalInterface
    public interface RequireSessionId {
        RequireMailboxId sessionId(MailboxSession.SessionId sessionId);
    }

    @FunctionalInterface
    public interface RequireMailboxId {
        FinalStage mailboxId(MailboxId mailboxId);
    }

    public static class FinalStage {
        ...
        ...
    }

    public static RequireUser builder() {
        return user -> sessionId -> mailboxId -> new FinalStage(user, sessionId, mailboxId);
    }
}

此注解将一个阶段可以拥有的方法数量限制为一个,以及具有默认实现的重载方法。无论如何,每个阶段都处理一个属性可能是一个好主意,但对于我目前的需求,我希望有多个方法在单独的类中实现。

问题

这让我想知道:我的舞台也应该有@FunctionalInterface 吗?在函数式编程中使用这样的构建器有什么好处/如何?

编辑

基于这个问题下面的 cmets,事实证明我真正想知道的是使阶段遵守功能接口合同的需求/好处是什么(不管可选注释)。

【问题讨论】:

  • 具体来说,该注解只是为了确保每个阶段都有一个抽象方法,以便每个阶段都可以使用 lambda 实现。它是契约的一部分,可以为您节省实现单个类的代码,并告诉其他人您确实打算以这种方式工作。
  • 阅读文章本身,我想说它本身就很好地概述了优点和缺点。从概念上讲,您不需要其中的任何内容,但是如果您想要像这样的分阶段构建器,那么按照我上面描述的方式进行注释会很有效。
  • 啊,当然……所以否则这是不可能的:return user -> sessionId -> mailboxId -> new FinalStage(user, sessionId, mailboxId);,对吧?
  • 不,您仍然可以从具有单一抽象方法且没有 @FunctionalInterface 注释的接口制作 lambdas。该注释的作用是告诉您该接口确实打算只有一个方法,并且添加或删除非默认方法会破坏您的合同。该合约也将由编译器验证,因为 JLS 要求这样做。但是,如果 RequireUser 没有遵循函数式接口契约并且有两个抽象方法,则创建 FinalStage 的最后一个代码是不可能的。
  • 我的意思是:为了使静态builder() 方法中的lamba 工作,阶段是否需要是功能接口(无论是否存在注释)?

标签: java functional-programming functional-interface


【解决方案1】:

单方法所需阶段类型用于强制调用者提供所需值,由编译器强制执行(它也强制执行特定顺序)。然后FinalStage 类具有所有可选方法。

在这种情况下,在调用builder() 之后,您必须调用user(...),然后是sessionId(...),然后是mailboxId(...),最后是FinalStage 中定义的任何可选方法: p>

MailboxCreatedBuilder.builder()
        .user(...)
        .sessionId(...)
        .mailboxId(...)
        ...
        .build();

【讨论】:

  • 这很好地解释了分阶段构建器模式如何工作以及如何使用它。我的问题是关于@FunctionalInterface 在这种模式下的好处。事实证明,我的问题实际上是关于为什么阶段需要遵循功能接口合同 :) 如果您可以编辑您的答案以合并您在我的问题下方所做的评论,那么我会将其作为接受的答案。
  • @RolfW。我没有在你的问题下面做任何 cmets。
  • @RolfW。 “我的问题实际上是关于为什么阶段需要遵循功能接口契约” 好吧,他们不需要,这样会更容易,因为您不必明确地将捕获的值向前推进进入下一阶段。
  • 哦,我明白了。搞混了:)
【解决方案2】:

在这种模式中,您的 RequireUserRequireSessionIdRequireMailboxId 需要是函数式接口,以便 MailboxCreatedBuilder 类型的 builder() 方法看起来像

public static RequireUser build() {
    return user -> sessionId -> mailboxId -> new FinalStage(user, sessionId, mailboxId);
}

如果任何阶段不遵循功能接口契约(即,它不是单一抽象方法接口),那么这条链就会中断,您需要为该阶段提供一个具体的非抽象类,并且可能之后的所有阶段。

@FunctionalInterface 注释在此模式中是可选的(因为您可以使用单个抽象方法从任何接口生成 lambda),它旨在通知您的同事和编译器这些类确实需要具有功能整个设置工作的接口。拥有注解会让您受益,因为 JLS 需要所有 Java 编译器来验证具有该注解的类型上的功能接口协定,因此当您不小心破坏协定时,您会得到很好的编译器错误。

【讨论】:

  • 这是builder() 方法,不是build() 方法,它属于MailboxCreatedBuilder 类,而不是FinalStage 类,从事实上,它是一个创建FinalStage 对象的非静态方法。
  • @Andreas,是的,我分心了,把这两个课程搞混了。现在已经修好了。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-06-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多