流畅和稳定的API的Lambda

几周前,我写了关于Java 8 lambda介绍 。 在本简介中,我解释了什么是lambda以及如何将它们与Java 8中也引入的新Stream API结合使用。

Stream API为集合提供了更实用的接口。 此接口在很大程度上取决于lambda。 但是,lambda不仅具有改进的收集处理能力,还具有更多优势

Lambda为您提供了构建更流畅的API的机会。 为了说明这一点,作为示例,我喜欢使用UserStore ,它有助于使用数据库获取和保存用户。 它的公共API通常如下所示。

public interface UserStore {
  User find(Long id);
  List<User> findByLastname(String lastname);
  List<User> findByCompany(String company);
  ..
}

findBy方法的列表通常比我在此处包括的两个方法更长。 随着系统的发展,可能还会有其他人。 尽管可行,但实际上所有这些方法都可以完成相同的事情。 他们返回具有匹配特定值的属性的所有用户。

一些框架提供了解决此问题的方法。 如果您使用过Hibernate,您可能会知道它们通过findByExample提供了解决方法,其中您提供了User作为示例对象,提供了查询的属性和值。 使用此示例对象中设置的任何值进行查询,而从查询中排除任何为null字段。 您可以稍微调整一下这种行为,但是这种方法存在许多问题。 考虑默认值,必填字段(即无法填写的字段)
null )和不变性。 iBatis,MyBatis以及Spring Data使用代码生成来节省您实现所有这些方法的时间,而使API充满了findBy方法列表。

这些变通办法可能会走很长一段路,但是它们确实留下了自己的特定问题。 另一种方法是使用lambda。

Lambda可以帮助我们将查询部分与过滤器规范分离。 让我们将findBy函数更改为接受lambda的单个函数。

public interface UserStore {
  User find(Long id);
  List<User> findBy(Predicate<User> p);
}

那是一个更好的API。 显然,谓词检查User对象有点天真。 您通常希望使用数据库查询进行过滤。 尽管如此,它仍然很好地满足了本示例的目的,您可以尝试使用自己的lambda来使用数据库查询进行过滤。 [注意: Predicate是Java 8附带的,位于java.util.function包中。

至少在以前的API中,这些谓词被捆绑在一个地方之前,您可能会生气,我们仍然可以捆绑(通用)谓词。 例如,通过创建一个包含它们的实用程序类UserPredicates

public final class UserPredicates {
  public static Predicate<User> lastname(String matcher) {
    return candidate -> matcher.equals(candidate.getLastname());
  }
}

使用新的UserStore API变得非常简单。

static import UserPredicates.lastname;

userStore.findBy(lastname("<lastname>");

不过, UserStore中还有一件事确实让我感到困扰。 find(id)函数返回一个用户。 但是,如果没有这样的用户呢?

可选的

为了对此进行改进,我们可以(并且应该)查看Java 8的另一个新功能,Optional。 这是monad的Java实现。 它看起来很像Scala的Option

使用Optional我们可以更好地表示一个函数可以返回一个值,但不一定返回一个值,并防止使用null 。 在我们的find(id)示例中,返回Optional明确表示我们可能找到具有所请求ID的用户,但可能不存在这样的用户。

public interface UserStore {
  Optional<User> find(Long id);
  List<User> findBy(Predicate p);
}

该API现在不仅记录了您可能会获得用户的事实,而且从未返回null 。 我认为永不返回null的API更加安全。 有一天,一个新的程序员可能没有意识到find可以返回null并且结果是一个null指针异常。 只是希望团队能够在生产之前就抓住它。 只要不使用null ,就很容易防止空指针异常。

我们可以使用Optional上的函数从用户那里获取一个值(如果有的话),或者从一个默认值中获取一个值。 例如,为了安全地获取用户的姓氏,我们编写以下内容。

Optional<User> user = userStore.find(id);
String lastname = user.map(User::getLastname).orElse("");

这段代码表达力很强,不需要太多解释。 如果有用户,请获取其姓氏。 否则,获取一个空字符串。

如果我们需要向用户发送密码重置电子邮件(如果找到)怎么办?

Optional<User> user = userStore.find(id);
user.ifPresent(passwordReset::send);

如果找到用户,则发送密码重置,否则什么也没有发生。

由于Java不像其他可能提供的其他语言(例如Haskell,Clojure和Scala)那样支持解构,因此我们仅限于Optional的功能。 这使得Optional比任何一种其他语言的等效功能都弱。

建造者

当然,不仅存储库的API都可以从lambda中受益。 Optional也是受益于lambda的API的一个很好的示例。 就我个人而言,我还发现lambda特别有用,可以代替过去的过往建造者。 通常不通过将特定的构建器传递给函数,而是通过从函数中生成一个构建器来改善去耦。 让我向您展示一个示例,用于发送电子邮件以阐明该想法。

public interface Mailer {
  void sendTextMessage(TextMessageBuilder message);
  void sendMimeMessage(MimeMessageBuilder message);
}

要使用Mailer我们需要将特定的构建器传递给它。 这些构建器具有通用的界面,但是它们构建的消息类型不同。 Mailer具有不同的方法,因为它必须根据所使用的类型添加不同的信息。 因此,任何客户端代码都紧密耦合以传递正确的构建器。

您可能会怀疑,这是lambda有用的地方。 Mailer函数可以创建所需的生成器并将其产生给lambda,而不是要求客户端创建生成器并将其传递给客户端。

public interface Mailer {
  void sendTextMessage(MessageConfigurator configurator);
  void sendMimeMessage(MessageConfigurator configurator);

  @FunctionalInterface
  interface MessageConfigurator {
    MessageBuilder configure(MessageBuilder message);
  }
}

要使用Mailer我们要做的就是提供一个lambda来构建消息。

mailer.sendTextMessage(message ->
  message.from(sender).to(recipients)
      .subject("APIs")
      .body("Lambdas can make for more fluent and stable APIs")
);

API现在更加稳定。 客户端代码与特定构建器中的任何更改都脱钩,并且只要构建器上的功能保持兼容就不会中断。

正如示例帮助我展示的那样,lambda可以帮助您构建更流畅和稳定的API,这些API更具意图。 这些API不需要太多的文档供其他程序员使用,因为实际上很难使它们弄错。 作为一般准则,我更喜欢清晰明了的代码而不是文档。 修复,不记录。

当然,我在本文中仅显示了一些示例。 Lambda不仅适用于此处的示例,而且适用范围更广。 我希望本文能为您提供一些有关lambda可以帮助您的新见解,并希望您能想到它们如何改善您的代码。


翻译自: https://www.javacodegeeks.com/2013/11/lambdas-for-fluent-and-stable-apis.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值