Необязательная монада и закон Деметры в 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() возвращает Optional<Address>. Следуя известному закону Деметры (LoD), приведенный выше код не является чистым. Однако я не могу понять, как реорганизовать его, чтобы сделать его чище.

В качестве первой попытки можно было бы добавить к классу User метод hasAddress(), но этот метод преодолевает необходимость наличия Optional<Address>, ИМО.

Как мне реорганизовать приведенный выше код? В таком случае стоит ли удовлетворять LoD?

EDIT: я пропустил указание, что в методе map я не хочу возвращать адрес.


person riccardo.cardin    schedule 17.11.2017    source источник
comment
Если ваш код ясен, я бы не беспокоился об этом.   -  person khelwood    schedule 17.11.2017
comment
Я думаю, что LoD и понятность кода — понятия параллельные. Первый пытается уменьшить зависимости между классами, а второй пытается сделать код более читабельным.   -  person riccardo.cardin    schedule 17.11.2017
comment
Не будьте догматичны, будьте прагматичны: если вы хотите проверить что-то о свойстве user, сделайте это. Глубина всего 2, а не user.getAddress().getThing().getThong().getThother().getThomethingElthe(). Если бы это было последнее, я бы беспокоился; как есть, просто иди с тем, что у тебя есть.   -  person Andy Turner    schedule 17.11.2017
comment
Хорошо, ты прав. Это простительный грех. Но если я хочу соответствовать LoD, какой рефакторинг мне следует выполнить?   -  person riccardo.cardin    schedule 17.11.2017
comment
Вы можете добавить еще одно логическое поле в user, которое говорит isAddressPresent, но это кажется излишним...   -  person Nir Alfasi    schedule 17.11.2017
comment
Я думаю, это зависит от того, что вы делаете внутри карты и в каком классе находится этот код. На мой взгляд, фильтрация списка пользователей на основании того, что у них есть адрес, сама по себе может быть нарушением ООП. Если затем вы сделаете что-то внутри этой карты (это зависит от того, что именно), вы можете рассматривать класс User как простую структуру данных, а не как правильный объект с некоторым поведением.   -  person matteobarbieri    schedule 17.11.2017
comment
Я думаю, вам следует добавить еще несколько аргументов в утверждение фильтрация списка пользователей на основании того факта, что у них есть адрес, что само по себе может быть нарушением ООП.   -  person riccardo.cardin    schedule 17.11.2017
comment
это действительно зависит от того, что вы делаете в map. Но если единственная причина, по которой вы выставляете address, состоит в том, чтобы проверить, присутствует ли он, я бы выбрал hasAddress   -  person matteobarbieri    schedule 17.11.2017


Ответы (5)


Ну, вы сами довольно хорошо резюмировали: если вы хотите более свободно соединяться, вводя hasAddress(), зачем возвращать Optional.

Читая то, что говорится в LoD, говорится об "ограниченных" знаниях о "тесно связанных" единицы. Звучит как серая зона для меня, но в дальнейшем здесь также упоминается правило «Только одна точка». Тем не менее, я бы согласился с комментариями к вашему вопросу о том, что нулевая проверка (или 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. Оба допустимы, а читаемость/обслуживаемость/чистота зависят от:

  • конкретный случай
  • насколько этот код открыт для других модулей
  • количество методов делегата, которые вам понадобятся в классе User
  • вашей архитектуры (например, если вы находитесь в классе UserDao, действительно ли вы хотите переместить доступ к базе данных в свой класс User POJO? Разве DAO не создан именно для этой цели? Квалифицируется ли это как «тесно связанный» и допускает нарушение правила «Только одна точка»?)
  • ...
person Malte Hartwig    schedule 17.11.2017

Не знаю, чище ли это (поскольку вам нужно использовать тот факт, что getAddress все равно возвращает необязательный параметр), но в Java 9 вы можете сделать:

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

or

users.stream()
    .flatMap(user -> user.getAddress().stream())
    .map(/* Some code */)
person pron    schedule 17.11.2017
comment
Ок, вы мне напомнили, что я пропустил указание, что в методе map я не хочу возвращать адрес. Извините, моя вина. - person riccardo.cardin; 17.11.2017

Думаю, вы уже сами ответили на этот вопрос.

Здесь у вас есть два разных варианта использования:

  1. Определить, есть ли у пользователя адрес
  2. Доступ к адресу пользователя нулевым безопасным способом

Первый вариант использования можно решить, добавив метод hasAddress(), как вы сказали. Второй вариант использования можно решить, используя Optional для переноса адреса. Там нет ничего плохого.

person Hugo Madureira    schedule 17.11.2017
comment
Однако я могу решить второй вариант использования, используя первый;) - person riccardo.cardin; 17.11.2017
comment
Решая второй вариант использования с помощью первого, вам нужно будет использовать оператор if, который будет возвращаться к доступу к адресу не нулевым безопасным способом. - person Hugo Madureira; 17.11.2017
comment
Использование правильного оператора if действительно null-safe. Это не чисто. - person riccardo.cardin; 17.11.2017

о чем LoD просит вас подумать, так это о том, нужно ли вообще выдавать значение адреса (т. е. запрашивать/доступно). Если ответ ДА ​​- тогда вам нужно иметь дело с нулевым или пустым случаем значения; в противном случае можно подумать о том, можно ли запросить существование адреса или нет.

Таким образом, если он действителен для выдачи адреса, то возврат Optional обрабатывает случай нулевого значения и может использоваться для подтверждения существования. Мне кажется, что проверка необязательности на «пустоту» аналогична проверке целого числа на 0 — вы не получаете доступ к атрибуту какого-либо другого объекта, а просто к атрибуту объекта, который вам дали.

Если разрешено знать только существование, то, вероятно, лучше использовать метод isPresent. Конечно, если вы имеете дело со значением SQL, то, возможно, потребуется необязательный параметр для обработки значения SQL NULL.

Иначе почему здесь происходит эта фильтрация?

person Jay    schedule 20.11.2017

Другой подход заключается в переносе вычислений в пользовательский класс.

Поскольку у пользователя есть адрес, он должен иметь возможность отвечать на вопросы об адресе и не должен раскрывать его. Таким образом, вы удовлетворите LoD

person Community    schedule 29.12.2017