Этот пост был опубликован на Hacker News с несколькими отзывами. Исходя из C ++, большинство этих примеров, кажется, идут вразрез с тем, чему меня учили.
Например, пример № 2:
Плохо:
def check_for_overheating(system_monitor)
if system_monitor.temperature > 100
system_monitor.sound_alarms
end
end
по сравнению с хорошим:
system_monitor.check_for_overheating
class SystemMonitor
def check_for_overheating
if temperature > 100
sound_alarms
end
end
end
Совет в C ++ заключается в том, что вы должны отдавать предпочтение свободным функциям, а не функциям-членам, поскольку они увеличивают инкапсуляцию Оба они семантически идентичны, так почему же предпочитаете выбор, который имеет доступ к большему количеству состояний?
Пример 4:
Плохо:
def street_name(user)
if user.address
user.address.street_name
else
'No street name on file'
end
end
по сравнению с хорошим:
def street_name(user)
user.address.street_name
end
class User
def address
@address || NullAddress.new
end
end
class NullAddress
def street_name
'No street name on file'
end
end
Почему это является обязанностью User
форматировать несвязанную строку ошибки? Что если я захочу сделать что-то кроме печати, 'No street name on file'
если на ней нет улицы? Что если улица названа так же?
Может ли кто-нибудь рассказать мне о преимуществах и объяснениях «говори, не спрашивай»? Я не ищу что лучше, а пытаюсь понять точку зрения автора.
источник
Ответы:
Запрос объекта о его состоянии, а затем вызов методов для этого объекта на основе решений, принятых за пределами объекта, означает, что объект теперь является утечкой абстракции; часть его поведения находится за пределами объекта, а внутреннее состояние подвергается (возможно, излишне) внешнему миру.
http://pragprog.com/articles/tell-dont-ask
источник
Как правило, в статье говорится, что вы не должны подвергать другие страны участию в рассуждениях, если вы можете сами об этом рассуждать .
Однако, что четко не указано, так это то, что этот закон попадает в очень очевидные пределы, когда аргументация лежит в основе ответственности конкретного класса. Например, каждый класс, чья работа состоит в том, чтобы хранить какое-то значение или предоставлять какое-то значение, особенно общее, или где класс обеспечивает поведение, которое должно быть расширено.
Например, если система предоставляет в
temperature
качестве запроса, то завтра клиент можетcheck_for_underheating
без необходимости вносить измененияSystemMonitor
. Это не тот случай, когдаSystemMonitor
реализуетcheck_for_overheating
сам. Таким образом,SystemMonitor
класс, чья работа состоит в том, чтобы поднять тревогу, когда температура слишком высока, действительно следует этому, ноSystemMonitor
класс, работа которого состоит в том, чтобы позволить другому коду кодировать температуру, чтобы он мог управлять, скажем, TurboBoost или чем-то подобным , не следует.Также обратите внимание, что во втором примере бессмысленно используется анти-шаблон Null Object.
источник
check_for_underheating
без необходимости менятьSystemMonitor
». Чем клиент отличается отSystemMonitor
этого? Разве вы сейчас не распределяете свою логику мониторинга по нескольким классам? Я также не вижу проблемы с классом монитора, который предоставляет сенсорную информацию другим классам, сохраняя при этом функции сигнализации для себя. Контроллер наддува должен контролировать наддув без необходимости беспокоиться о срабатывании сигнализации, если температура становится слишком высокой.Реальная проблема с вашим примером перегрева заключается в том, что правила для того, что квалифицируется как перегрев, нелегко варьировать для разных систем. Предположим, что система A такая же, как у вас (температура> 100 перегревается), но система B более чувствительная (температура> 93 перегревается). Измените ли вы свою функцию управления для проверки типа системы, а затем примените правильное значение?
Или у вас каждый тип системы определяет свою теплопроизводительность?
РЕДАКТИРОВАТЬ:
Первый способ делает вашу управляющую функцию уродливой, когда вы начинаете работать с большим количеством систем. Последнее позволяет функции управления быть стабильной с течением времени.
источник
Прежде всего, я чувствую, что должен исключить, что вы охарактеризовали примеры как «плохие» и «хорошие». В статье используются термины «Не очень хорошо» и «Лучше», я думаю, что эти термины были выбраны по определенной причине: это руководящие принципы, и в зависимости от обстоятельств подход «Не очень хорошо» может быть подходящим или даже единственным решением.
Когда вам предоставляется выбор, вы должны отдавать предпочтение включению любой функциональности, которая полагается исключительно на класс в классе, а не вне его - причина заключается в инкапсуляции и в том, что с течением времени это облегчает развитие класса. Класс также лучше рекламирует свои возможности, чем набор бесплатных функций.
Иногда вам нужно сказать, потому что решение зависит от чего-то вне класса или потому, что вы просто не хотите, чтобы большинство пользователей класса делали это. Иногда вы хотите сказать, потому что поведение является противоинтуитивным для класса, и вы не хотите путать большинство пользователей класса.
Например, вы жалуетесь на то, что уличный адрес возвращает сообщение об ошибке, это не то, что он делает, предоставляет значение по умолчанию. Но иногда значение по умолчанию не подходит. Если это был Штат или Город, вы можете использовать значение по умолчанию при назначении записи продавцу или лицу, проводящему опрос, чтобы все неизвестные лица передавались конкретному человеку. С другой стороны, если вы печатаете конверты, вы можете предпочесть исключение или охрану, которая не дает вам тратить бумагу на письма, которые невозможно доставить.
Так что могут быть случаи, когда «Не так хорошо» - это путь, но обычно «Лучше», ну, лучше.
источник
Антисимметрия данных / объектов
Как отмечали другие, Tell-Dont-Ask специально предназначен для случаев, когда вы изменяете состояние объекта после того, как спросили (см., Например, текст Pragprog, размещенный в другом месте на этой странице). Это не всегда так, например, объект 'user' не изменяется после того, как его спросили о его user.address. Это поэтому спорны , если это является подходящим случаем для применения Tell-Dont-прод.
Tell-Dont-Ask занимается вопросами ответственности, а не вытягиванием логики из класса, который должен быть обоснованно внутри него. Но не вся логика, которая имеет дело с объектами, обязательно является логикой этих объектов. Это намекает на более глубоком уровне, даже за пределами Tell-Dont-Ask, и я хочу добавить краткое замечание об этом.
Что касается архитектурного проектирования, вы можете захотеть иметь объекты, которые на самом деле являются просто контейнерами для свойств, может быть даже неизменяемыми, а затем запускать различные функции над коллекциями таких объектов, оценивая, фильтруя или преобразовывая их, а не отправляя им команды (что является больше домен Телль-Дон-Аск).
Решение, более подходящее для вашей проблемы, зависит от того, ожидаете ли вы иметь стабильные данные (декларативные объекты), но с изменением / добавлением на стороне функции. Или если вы ожидаете, что у вас будет стабильный и ограниченный набор таких функций, но вы ожидаете большего потока на уровне объектов, например, путем добавления новых типов. В первой ситуации вы бы предпочли свободные функции, во втором - методы объекта.
Боб Мартин в своей книге «Чистый код» называет это «Антисимметрией данных / объектов» (стр. 95 и далее), другие сообщества могут называть это « проблемой выражения ».
источник
Эта парадигма иногда упоминается как «Скажи, не спрашивай» , что означает сказать объекту, что делать, не спрашивать о его состоянии; и иногда как «Спроси, не говори» , то есть попросите, чтобы объект сделал что-то для вас, не говорите, в каком состоянии он находится. В любом случае, передовой опыт одинаков - способ, которым объект должен выполнить действие, - это забота этого объекта, а не вызывающего объекта. Интерфейсы должны избегать выставления своего состояния (например, через средства доступа или открытые свойства) и вместо этого выставлять «делающие» методы, реализация которых непрозрачна. Другие покрывали это ссылками на прагматичного программиста.
Это правило связано с правилом об избежании кода «двойная точка» или «двойная стрелка», часто называемого «разговаривать только с непосредственными друзьями», что говорит о недопустимости
foo->getBar()->doSomething()
; вместо этого используйте использование,foo->doSomething();
которое является вызовом обертки вокруг функциональности панели, и реализовано так простоreturn bar->doSomething();
- еслиfoo
отвечает за управлениеbar
, то пусть так и сделает!источник
В дополнение к другим хорошим ответам о «говорите, не спрашивайте», некоторые комментарии к вашим конкретным примерам, которые могут помочь:
Этот выбор не имеет доступа к большему количеству государства. Они оба используют одинаковое количество состояний для выполнения своей работы, но «плохой» пример требует, чтобы состояние класса было публичным, чтобы выполнять свою работу. Кроме того, поведение этого класса в «плохом» примере распространяется на функцию free, что затрудняет его поиск и усложняет рефакторинг.
Почему «street_name» несет ответственность за то, чтобы «получить название улицы» и «предоставить сообщение об ошибке»? По крайней мере, в «хорошей» версии каждая часть несет одну ответственность. Тем не менее, это не отличный пример.
источник
Эти ответы очень хороши, но вот еще один пример, чтобы подчеркнуть: обратите внимание, что обычно это способ избежать дублирования. Например, предположим, что у вас есть НЕСКОЛЬКО мест с кодом вроде:
Это означает, что вам лучше иметь что-то вроде этого:
Поскольку это дублирование означает, что большинство клиентов вашего интерфейса будут использовать новый метод, вместо того, чтобы повторять одну и ту же логику здесь и там. Вы даете своему делегату ту работу, которую вы хотите сделать, вместо того, чтобы запрашивать информацию, которая вам нужна, чтобы сделать это самостоятельно.
источник
Я считаю, что это более верно при написании высокоуровневого объекта, но менее верно при переходе на более глубокий уровень, например, библиотеку классов, так как невозможно написать каждый отдельный метод для удовлетворения всех потребителей классов.
Например # 2, я думаю, что это слишком упрощено. Если бы мы действительно собирались реализовать это, SystemMonitor в конечном итоге имел бы код для низкоуровневого доступа к оборудованию и логику для высокоуровневой абстракции, встроенную в тот же класс. К сожалению, если бы мы попытались разделить это на два класса, мы бы нарушили само слово «говори, не спрашивай».
Пример № 4 более или менее одинаков - он встраивает логику пользовательского интерфейса в уровень данных. Теперь, если мы собираемся исправить то, что пользователь хочет видеть в случае отсутствия адреса, мы должны исправить объект на уровне данных, и что если два проекта используют один и тот же объект, но нужно использовать разный текст для нулевого адреса?
Я согласен, что если бы мы могли реализовать «Скажи, не проси» обо всем, это было бы очень полезно - я сам был бы счастлив, если бы я мог просто сказать, а не спрашивать (и делать это сам) в реальной жизни! Однако, как и в реальной жизни, выполнимость решения очень ограничена классами высокого уровня.
источник