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" 规则?)
- ...
我相信你自己已经回答了这个问题。
这里有两个不同的用例:
- 判断用户是否有地址
- 以空安全方式访问用户地址
第一个用例可以通过添加方法 hasAddress()
来解决,如您所述。
第二个用例可以使用 Optional
来包装地址来解决。这没有错。
LoD 要求您考虑的是是否需要给出地址的值(即 requested/accessed)。如果答案是肯定的——那么你需要处理 null 或空值的情况;否则,你可以考虑是否可以请求地址存在。
因此,如果它对于要给出的地址有效,则返回一个 Optional 处理空值情况,并可用于确定是否存在。在我看来,检查 'emptiness' 的可选项与检查 0 的整数是一样的——你不是在访问其他某个对象的属性,只是你被赋予的对象的属性。
如果只允许知道存在,那么 isPresent 方法可能会更好。当然,如果你正在处理一个 SQL 值,那么可能需要一个 Optional 来处理 SQL NULL 值。
否则,为什么要在这里进行过滤?
另一种方法是将计算移交给用户 class。
由于用户拥有地址,他应该能够回答有关地址的问题而不必公开地址。这样你就可以满足 LoD
当我审查一些代码时,我看到了这个片段。
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" 规则?)
- ...
我相信你自己已经回答了这个问题。
这里有两个不同的用例:
- 判断用户是否有地址
- 以空安全方式访问用户地址
第一个用例可以通过添加方法 hasAddress()
来解决,如您所述。
第二个用例可以使用 Optional
来包装地址来解决。这没有错。
LoD 要求您考虑的是是否需要给出地址的值(即 requested/accessed)。如果答案是肯定的——那么你需要处理 null 或空值的情况;否则,你可以考虑是否可以请求地址存在。
因此,如果它对于要给出的地址有效,则返回一个 Optional 处理空值情况,并可用于确定是否存在。在我看来,检查 'emptiness' 的可选项与检查 0 的整数是一样的——你不是在访问其他某个对象的属性,只是你被赋予的对象的属性。
如果只允许知道存在,那么 isPresent 方法可能会更好。当然,如果你正在处理一个 SQL 值,那么可能需要一个 Optional 来处理 SQL NULL 值。
否则,为什么要在这里进行过滤?
另一种方法是将计算移交给用户 class。
由于用户拥有地址,他应该能够回答有关地址的问题而不必公开地址。这样你就可以满足 LoD