可选的monad和Java中的Demeter法则

时间:2017-11-17 09:18:50

标签: java refactoring optional law-of-demeter

在我查看一些代码时,我遇到了这个代码段。

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

方法user.getAddress()的调用会返回Optional<Address>。遵循着名的德米特法则(LoD),上面的代码并不干净。但是,我无法弄清楚如何重构它以使其更清洁。

首次尝试可能是向User类添加方法hasAddress(),但此方法无需使用Optional<Address>,IMO。

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

编辑:我错过了在map方法中指定我不想返回地址。

5 个答案:

答案 0 :(得分:3)

嗯,你自己总结得很好:如果你想通过介绍hasAddress()来松散地结合,为什么要返回Optional

Reading into what the LoD says,它谈论的是&#34;限制&#34;关于&#34;密切相关的知识&#34;单位。听起来像是一个灰色区域,但进一步说它还提到了#34;只有一个点&#34;规则。尽管如此,我同意你的问题的评论,即空检查(或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
        });
    }
}

但是,正如评论者指出的那样:是否值得/必要?所有这些法律/原则都不是绝对的,而是比教条更具指导性。在这种情况下,它可能是关于平衡LoD与KISS。

最后,您需要决定此特定示例是否会从将流的功能移动到User类中获益。两者都有效,可读性/可维护性/清洁度取决于:

  • 具体案例
  • 此代码对其他模块的暴露程度
  • 用户类
  • 中需要的委托方法数
  • 你的架构(例如,如果你在UserDao课程中,你真的想要移动数据库访问你的用户POJO课程吗?难道DAO不是为了这个目的而制作的吗?这是否符合&#34;密切相关的&#34;并允许违反&#34;只有一个点&#34;规则?)
  • ...

答案 1 :(得分:1)

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

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

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

答案 2 :(得分:0)

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

这里有两个不同的用例:

  1. 确定用户是否有地址
  2. 以零安全方式访问用户地址
  3. 第一个用例可以通过添加方法<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.hacker.timernotification"> <uses-permission android:name="com.android.alarm.permission.SET_ALARM"/> <uses-permission android:name="android.permission.WAKE_LOCK"/> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <activity android:name=".Repeating_activity"/> <receiver android:name=".Notification_receiver" android:directBootAware="true"> <intent-filter> <action android:name="android.intent.action.BOOT_COMPLETED" /> </intent-filter> </receiver> </application> 来解决,就像你说的那样。 可以使用{{1}}来解决第二个用例来包装地址。这没什么不对。

答案 3 :(得分:0)

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

因此,如果它对于要发出的地址有效,则返回Optional处理空值的情况,并可用于确定是否存在。检查可选的空虚&#39;在我看来,与检查0的整数是一样的 - 你没有访问某个其他对象的属性,只是你给出的对象的属性。

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

否则,为什么这个过滤发生在这里?

答案 4 :(得分:0)

另一种方法是将计算移动到用户类。

由于用户有地址,他应该能够回答有关地址的问题而不必暴露它。那样你就满足了LoD