Pull to refresh

Comments 27

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

http://www.yegor256.com/2016/07/18/law-of-demeter.html
UFO just landed and posted this here
Оригинал Law of Demeter (статья 1988 года) находится тут: Object-Oriented Programming: An Objective Sense of Style. Там есть уточняющие формулировки закона для Lisp, C++ и Eiffel.

Я в свое время думал написать плагин для LLVM, который бы делал проверку этого закона, но предварительные эксперименты по внедрению такой проверки в стандарты кодирования провалились. Причиной тому была не чистая функциональность используемых языков программирования (в данном случае это были C++ и Python) — из-за того, что объекты можно передавать по ссылке, контролировать операции над ними не представлялось возможным (особенно, если это были библиотечные вызовы). В итоге мы использовали другие критерии, как например, тестируемость кода и ограничение cyclomatic complexity.

А разве объект (внеся изменения в суперкласс) нельзя было сделать таким, чтобы он сам следил и "докладывал" о нарушениях "закона Деметры" в отношении себя — например, сообщал, что его передали по ссылке не тому, с кем ему можно общаться или наоборот — вызвал недопустимого объекта?

По-моему, в ней нет ничего альтернативного. Лишь более правильная интерпретация, чем наиболее распространённая. В приведённом там примере использование метода textOfLastPage() не устраняет необходимость вызывающего кода знать внутреннее устройство вызываемого объекта. Ведь этот метод не может в какой-то момент начать возвращать текст первой страницы вместо последней… или вообще не текст, а число, обозначающее количество слов на странице. Для этого метод придётся переименовать, поскольку внутреннее устройство и реализация закодированы прямо в его названии.

Мне кажется класс Money как кортеж «валюта-сумма» лучше бы был иммутабельным
Согласен, но почему вы решили, что он mutable?

У класса нет методов, меняющих его внутреннее состояние, а методы вроде subtract задуманы возвращать новый объект Money (результат вычисления).
Да, все верно, прошу прощения. Я слишком быстро и невнимательно читал :)
Можно вопрос — почему используется выражение moneyForProducts.subtract(actualSum), а не actualSum.subtract(moneyForProducts)?
Это опечатка, спасибо что нашли её — исправил)
А что делать если метод sell требует и Wallet и Money?
Если метод sell требует Wallet — это проблема дизайна.
Зависимость от Wallet в методе sell — это как раз то, от чего мы пытались уйти.
Метод нужен для осуществления продажи и должен зависеть от денег, но никак не от хранилища денег.
И все же предположим он нужен и с дизайном все хорошо. Мой вопрос не о дизайне метода sell и не о самом методе. а о том как решить проблему если есть такая вот зависимость от Wallet и Money
Если методу sell для продажи понадобился Wallet, то с дизайном всё же не очень хорошо. Если вы считаете, что существует ситуация, при которой sell должен знать о Wallet, и это хороший дизайн, пожалуйста, приведите конкретный пример, и тогда я смогу ответить.
Вот пример. Бизнесаналитик говорит что в момент продажи необходимо помимо всего прочего вызвать еще и метод doSomethingImportant() из Wallet.
Как быть в этой ситуации? Как поступите?
> Если метод sell требует Wallet — это проблема дизайна.

Если метод sell требует Money — это проблема дизайна.

Не знаю, почему у вас sell возвращает остаток денег в кошельке. Обычно при продаже формируется чек. Там есть не только деньги, но и счета списания и зачисления.

Где у вас защита от race condition?
> Не знаю, почему у вас sell возвращает остаток денег в кошельке

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

> Обычно при продаже формируется чек
> Где у вас защита от race condition?

Цель этой статьи — показать пользу от применения закона в отдельно взятом, выдуманном случае. Этот случай недостаточно реален для того, чтобы он ровно в таком же виде возник у вас на проекте, но достаточно реален для того, чтобы из него можно было сделать какие-то выводы.

Я не ставил перед собой цели писать thread-safe пример, т.к. лишние строчки обработки race-condition'ов отвлекали бы читателя от основной мысли, которую я хотел донести. Я считаю, что это допустимо в рамках примера, хотя, возможно, следовало упомянуть об этом в начале статьи.

Всё же статья смотрелась бы серьёзнее, если бы пример был не выдуманный, а реальный. Меня, например, статья не убедила, что преобразования сделали код лучше.

> Второй (более корректный пример) взаимодействует только с деньгами и возвращает сдачу.

У вас бизнес-процесс поменялся, что это стало «сдачей»? Сдача не с содержимого кошелька, а с одной из купюр из кошелька. У вас в примере не сдача.

Я напомню, что тред начался с «А что делать если метод sell требует и Wallet и Money?». И иметь и то и другое — нормально, если это не отдельно взятый выдуманный случай.

Если вы выдумываете плохие примеры — не надо с других спрашивать нормальные.
Сложно что — либо возразить — пример действительно не очень удачный.
Я это не к тому, что пример плох, а к тому, что вопрос gzhernov вполне корректен и на него надо бы ответить. Если есть что.
А вам не кажется что методу subtract самое место в классе Wallet?

Зачем объекту Seller знать сколько денег находится в кошельке и тем более пытаться самому их оттуда извлечь, если его дело — посчитать суммарную стоимость всех продуктов и сказать «кошельку», сколько тот должен заплатить.
Хотя, конечно, продавец скорее должен взаимодействовать с покупателем, а вовсе не с его кошельком напрямую.
А меня больше смущает, что если передавать wallet в метод, то совершенно не ясно, изменяется ли его состояние внутри или нет
public Money sell(Set<Product> products, Wallet wallet) throws NotEnoughMoneyException

в отличие от
public Money sell(Set<Product> products, Money moneyForProducts) throws NotEnoughMoneyException

когда априори, изменяться нечему

В первом случае по-хорошему состояние изменяться должно, потому что метод называется sell. То что оно не меняется, очень странно.


Второй метод sell, который производит вычитание, по факту ничего не продаёт. Он мне нравится ещё меньше, чем первый, потому что непонятно, зачем он такой нужен.


А вообще спорные вопросы вроде изменяется ли передаваемый объект, стоит аккуратно расписывать в JavaDoc. Тогда всё будет хорошо.

Спасибо за ваш комментарий, вы правы.
Вы правы. Но я отдал предпочтение 2ому варианту исходя из следующих мыслей:
 1) приводимый пример автором мне показался больше академическим, нежели боевым, иначе тогда нужно
    упомянуть вопросы логирования, рейс-кондишина, отката и др.
 2) первый вариант показался бы странным, если объект Wallet был неизменяемым, если же он изменяемый, 
    то зачем тогда возвращать остаток?
 
 Но все равно, теперь вы заставили меня сомневаться, что лучше?
Адресовано lany, никак не могу покорить систем комментариев здесь
Sign up to leave a comment.

Articles