Java 中的可选 monad 和 Demeter 法则

Optional monad and the Law of Demeter in Java

当我审查一些代码时,我看到了这个片段。

List<User> users = /* Some code that initializes the list */;
users.stream()
     .filter(user -> user.getAddress().isPresent())
     .map(/* Some code */)
// And so on...

方法 user.getAddress() return 的调用是 Optional<Address>。遵循著名的 Demeter 法则 (LoD),上面的代码并不干净。但是,我不知道如何重构它以使其更干净。

作为第一个尝试可能是向 User class 添加一个方法 hasAddress(),但此方法克服了 Optional<Address>,IMO 的需要。

我该如何重构上面的代码?在这种情况下,是否值得满足LoD?

编辑:我没有在 map 方法中指定我不想 return 地址。

不知道它是否更干净(因为你需要使用 getAddress returns 一个可选的事实),但在 Java 9 中你可以这样做:

users.stream()
    .map(User::getAddress)
    .flatMap(Optional::stream)
    .map(/* Some code */)

users.stream()
    .flatMap(user -> user.getAddress().stream())
    .map(/* Some code */)

嗯,你自己总结得很好:如果你想通过引入 hasAddress() 来更松散地耦合,为什么 return 和 Optional.

Reading into what the LoD says,它讲的是 "limited" 关于 "closely related" 单位的知识。对我来说这听起来像是一个灰色地带,但更进一步它还提到了 "Only one dot" 规则。不过,我同意对您的问题的评论,即空检查(或 isPresent())完全没问题(哎呀,技术上真正的空检查甚至不需要点 ;P )。

如果你真的想封装更多,你可以完全删除 getAddress() 并提供:

class User {
    private Optional<Address> address;

    boolean hasAddress() {
        return address.isPresent();
    }

    // still exposes address to the consumer, guard your properties
    void ifAddressPresent(Consumer<Address> then) {
        address.ifPresent(then::accept);
    }

    // does not expose address, but caller has no info about it
    void ifAddressPresent(Runnable then) {
        address.ifPresent(address -> then.run());
    }

    // really keep everything to yourself, allowing no outside interference
    void ifAddressPresentDoSomeSpecificAction() {
        address.ifPresent(address -> {
            // do this
            // do that
        });
    }
}

但是,正如评论者指出的那样:它值得 it/necessary 吗?所有这些 laws/principles 很少是绝对的,比教条更具有指导意义。在这种情况下,它可能是关于平衡 LoD 与 KISS。


最后,由您决定此特定示例是否受益于将流的功能移至用户 class。两者都有效,并且 readability/maintainability/cleanliness 取决于:

  • 具体案例
  • 此代码对其他模块的公开程度
  • 用户中需要的委托方法的数量class
  • 您的体系结构(例如,如果您在 UserDao class 中,您真的要移动数据库访问您的用户 POJO class 吗?DAO 不正是为此目的而创建的吗?这是否符合 "closely related" 并允许违反 "Only one dot" 规则?)
  • ...

我相信你自己已经回答了这个问题。

这里有两个不同的用例:

  1. 判断用户是否有地址
  2. 以空安全方式访问用户地址

第一个用例可以通过添加方法 hasAddress() 来解决,如您所述。 第二个用例可以使用 Optional 来包装地址来解决。这没有错。

LoD 要求您考虑的是是否需要给出地址的值(即 requested/accessed)。如果答案是肯定的——那么你需要处理 null 或空值的情况;否则,你可以考虑是否可以请求地址存在。

因此,如果它对于要给出的地址有效,则返回一个 Optional 处理空值情况,并可用于确定是否存在。在我看来,检查 'emptiness' 的可选项与检查 0 的整数是一样的——你不是在访问其他某个对象的属性,只是你被赋予的对象的属性。

如果只允许知道存在,那么 isPresent 方法可能会更好。当然,如果你正在处理一个 SQL 值,那么可能需要一个 Optional 来处理 SQL NULL 值。

否则,为什么要在这里进行过滤?

另一种方法是将计算移交给用户 class。

由于用户拥有地址,他应该能够回答有关地址的问题而不必公开地址。这样你就可以满足 LoD