Я читаю книгу под названием Rails AntiPatterns, и они говорят об использовании делегирования, чтобы избежать нарушения закона Деметры. Вот их главный пример:
Они считают, что вызывать что-то подобное в контроллере плохо (и я согласен)
@street = @invoice.customer.address.street
Их предлагаемое решение заключается в следующем:
class Customer
has_one :address
belongs_to :invoice
def street
address.street
end
end
class Invoice
has_one :customer
def customer_street
customer.street
end
end
@street = @invoice.customer_street
Они утверждают, что, поскольку вы используете только одну точку, вы не нарушаете здесь закон Деметры. Я думаю, что это неверно, потому что вы все еще проходите через клиента, чтобы пройти через адрес, чтобы получить улицу счета. Я прежде всего получил эту идею из сообщения в блоге, которое я прочитал:
http://www.dan-manges.com/blog/37
В блоге ярким примером является
class Wallet
attr_accessor :cash
end
class Customer
has_one :wallet
# attribute delegation
def cash
@wallet.cash
end
end
class Paperboy
def collect_money(customer, due_amount)
if customer.cash < due_ammount
raise InsufficientFundsError
else
customer.cash -= due_amount
@collected_amount += due_amount
end
end
end
В сообщении блога говорится, что, хотя customer.cash
вместо него есть только одна точка customer.wallet.cash
, этот код все еще нарушает Закон Деметры.
Теперь в методе Paperboy collect_money у нас нет двух точек, у нас есть только одна в «customer.cash». Эта делегация решила нашу проблему? Не за что. Если мы посмотрим на поведение, разносчик газет все еще лезет прямо в кошелек клиента, чтобы получить наличные.
РЕДАКТИРОВАТЬ
Я полностью понимаю и согласен с тем, что это все еще является нарушением, и мне нужно создать метод в Wallet
вызываемом выводе, который обрабатывает платеж для меня, и что я должен вызвать этот метод внутри Customer
класса. Что я не понимаю, так это то, что в соответствии с этим процессом мой первый пример все еще нарушает Закон Деметры, потому что Invoice
он все еще идет прямо Customer
к улице.
Может кто-нибудь помочь мне устранить путаницу. Я искал последние 2 дня, пытаясь раскрыть эту тему, но это все еще сбивает с толку.
источник
Ответы:
Ваш первый пример не нарушает закон Деметры. Да, с помощью кода в том виде, в каком он есть, он говорит, что
@invoice.customer_street
действительно получает то же значение, что и гипотетический@invoice.customer.address.street
, но на каждом шаге обхода возвращаемое значение определяется запрашиваемым объектом - это не значит, что «разносчик газет достигает кошелек клиента ", это то, что" разносчик запрашивает у клиента наличные, а клиент получает деньги из своего кошелька ".Когда вы говорите
@invoice.customer.address.street
, вы предполагаете знание клиента и адреса внутренних органов - это плохо. Когда вы говорите@invoice.customer_street
, вы спрашиваетеinvoice
: «Эй, я бы хотел улицу клиента, вы сами решаете, как вы ее получите ». Затем клиент говорит по своему адресу: «Эй, я бы хотел, чтобы на твоей улице ты сам решил, как ты ее получишь ».Суть Demeter заключается не в том, что «вы никогда не сможете узнать значения от объектов, находящихся далеко от вас в графе», а в том, что «вы сами не должны проходить далеко вдоль графа объектов, чтобы получить значения».
Я согласен, что это может показаться тонким различием, но учтите это: в коде, совместимом с Demeter, сколько кода нужно изменить, когда внутреннее представление
address
изменений меняется? Как насчет кода, не совместимого с Demeter?источник
Первый пример и второй на самом деле не очень одинаковы. В то время как первый говорит об общих правилах «одной точки», второй больше говорит о других вещах в ОО-дизайне, особенно « Скажи, не спрашивай »
Опять же, « только для поведения, а не для атрибутов »
Если вы спрашиваете об атрибутах, вы должны спросить . «Эй, парень, сколько у тебя денег в кармане? Покажи мне, я оценю, сможешь ли ты заплатить это». Это неправильно, ни один продавец не будет вести себя так. Вместо этого они скажут: «Пожалуйста, заплатите»
Заказчик будет обязан самостоятельно оценить, должен ли он платить и может ли он платить. И задача клерка закончена после того, как он сказал клиенту заплатить.
Итак, второй пример доказывает, что первый неверен?
По моему мнению. Нет , пока:
1. Вы делаете это с самоограничением.
Несмотря на то, что вы можете получить доступ ко всем атрибутам клиента с
@invoice
помощью делегирования, в обычных случаях вам это нужно редко.Подумайте о странице, показывающей счет в приложении Rails. Там будет раздел сверху, чтобы показать детали клиента. Так, в шаблоне счета, вы будете кодировать как это?
Это неправильно и неэффективно. Лучший подход
Затем пусть клиент частично обработает все атрибуты, принадлежащие клиенту.
Так что, как правило, вам это не нужно. Но у вас может быть страница со списком, отображающая все последние счета, в каждом из которых есть поле
li
с информацией о клиенте. В этом случае вам нужен атрибут клиента, чтобы показать, и это вполне законно, чтобы код шаблона2. Дальнейших действий в зависимости от вызова этого метода нет.
В приведенном выше случае со страницей списка в счете-фактуре запрашивался атрибут имени клиента, но его реальная цель - « покажи мне свое имя », так что это в основном поведение, а не атрибут . Нет никаких дальнейших оценок и действий, основанных на этом атрибуте, например, если вас зовут «Майк», я вас полюблю и предоставлю вам еще 30 дней кредита. Нет, счет просто скажи "покажи мне свое имя", не более. Так что это вполне приемлемо в соответствии с правилом «Не спрашивай» в примере 2.
источник
Читайте дальше во второй статье и я думаю, что идея прояснится. Идея заключается в том, чтобы клиент предлагал возможность платить и полностью скрывать, где хранится дело. Это поле, член кошелька или что-то еще? Вызывающая сторона не знает, не должна знать и не изменяется, если эта деталь реализации изменяется.
Поэтому я думаю, что ваш второй отзыв дает более полезную рекомендацию.
Идея «единой точки» является частичным успехом, поскольку она скрывает некоторые глубокие детали, но все же увеличивает связь между отдельными компонентами.
источник
Похоже, Дэн извлек свой пример из этой статьи: «Бумажник», «Кошелек» и «Закон Деметры»
Я понимаю, что ваш пример написан на Ruby, но это должно применяться ко всем языкам ООП.
источник