Принцип « Говори, а не спрашивай» гласит:
вы должны стараться рассказать объектам, что вы хотите, чтобы они делали; не задавайте им вопросов об их состоянии, примите решение, а затем скажите им, что делать.
Проблема заключается в том, что, как вызывающая сторона, вы не должны принимать решения, основанные на состоянии вызываемого объекта, в результате чего вы затем меняете состояние объекта. Логика, которую вы реализуете, вероятно, является ответственностью вызываемого объекта, а не вашей. Для вас принятие решений вне объекта нарушает его инкапсуляцию.
Простой пример «Скажи, не проси» является
Widget w = ...;
if (w.getParent() != null) {
Panel parent = w.getParent();
parent.remove(w);
}
и сказать версию ...
Widget w = ...;
w.removeFromParent();
Но что, если мне нужно узнать результат от метода removeFromParent? Моя первая реакция состояла в том, чтобы просто изменить removeFromParent, чтобы он возвращал логическое значение, обозначающее, был ли удален родитель или нет.
Но потом я наткнулся на шаблон разделения командных запросов, который говорит НЕ делать этого.
В нем говорится, что каждый метод должен быть либо командой, выполняющей действие, либо запросом, который возвращает данные вызывающей стороне, но не обоими. Другими словами, задание вопроса не должно изменить ответ. Более формально, методы должны возвращать значение, только если они прозрачны по ссылкам и, следовательно, не имеют побочных эффектов.
Эти два действительно противоречат друг другу, и как я могу выбрать между этими двумя? Я согласен с прагматичным программистом или Бертраном Мейером?
Ответы:
На самом деле ваш пример проблемы уже иллюстрирует отсутствие декомпозиции.
Давайте просто немного его изменим:
Это на самом деле не отличается, но делает недостаток более очевидным: почему книга знает о своей полке? Проще говоря, это не должно быть. Он вводит зависимость книг на полках (что не имеет смысла) и создает циклические ссылки. Это все плохо.
Аналогично, виджету не обязательно знать своего родителя. Вы скажете: «Хорошо, но виджету нужен родительский элемент для правильной компоновки и т. Д.» и под капотом виджет знает своего родителя и запрашивает у него метрики для расчета своих собственных метрик на основе их и т. д. Согласно скажите, не спрашивайте, что это неправильно. Родитель должен указать всем своим дочерним элементам, передавая всю необходимую информацию в качестве аргументов. Таким образом, можно легко иметь один и тот же виджет у двух родителей (независимо от того, имеет ли это смысл).
Чтобы вернуться к примеру с книгой - введите библиотекаря:
Пожалуйста, поймите, что мы больше не просим в смысле сказать, не спрашивайте .
В первой версии полка была собственностью книги, и вы просили об этом. Во второй версии мы не делаем такого предположения о книге. Все, что мы знаем, это то, что библиотекарь может рассказать нам полку с книгой. Предположительно, он полагается на какую-то справочную таблицу для этого, но мы не знаем наверняка (он мог бы просто просмотреть все книги на каждой полке), и на самом деле мы не хотим знать .
Мы не полагаемся на то, связан ли ответ библиотекарей с его состоянием или состоянием других объектов, от которых он может зависеть. Мы говорим библиотекарю найти полку.
В первом сценарии мы непосредственно закодировали отношения между книгой и полкой как часть состояния книги и получили это состояние напрямую. Это очень хрупко, потому что мы также делаем предположение, что полка, возвращаемая книгой, содержит книгу (это ограничение, которое мы должны обеспечить, иначе мы не сможем получить
remove
книгу с полки, на которой она фактически находится).С введением библиотекаря мы моделируем эти отношения отдельно, поэтому достигаем разделения интересов.
источник
Если вам нужно знать результат, тогда вы знаете; это ваше требование.
Фраза «методы должны возвращать значение только в том случае, если они прозрачны по ссылкам и, следовательно, не имеют побочных эффектов», является хорошим указанием (особенно если вы пишете свою программу в функциональном стиле, для параллелизма или по другим причинам), но это не абсолют.
Например, вам может потребоваться узнать результат операции записи в файл (true или false). Это пример метода, который возвращает значение, но всегда вызывает побочные эффекты; нет никакого способа обойти это.
Чтобы выполнить разделение команд / запросов, вам нужно будет выполнить файловую операцию с одним методом, а затем проверить ее результат с помощью другого метода, нежелательного метода, поскольку он отделяет результат от метода, который вызвал результат. Что если что-то случится с состоянием вашего объекта между вызовом файла и проверкой состояния?
Суть заключается в следующем: если вы используете результат вызова метода для принятия решений в другом месте программы, то вы не нарушаете «Скажите не спрашивать». Если, с другой стороны, вы принимаете решения для объекта на основе вызова метода для этого объекта, то вы должны переместить эти решения в сам объект, чтобы сохранить инкапсуляцию.
Это вовсе не противоречит разделению командных запросов; на самом деле, это дополнительно усиливает его, потому что больше нет необходимости выставлять внешний метод для целей статуса.
источник
Я думаю, что вы должны идти со своим первоначальным инстинктом. Иногда дизайн должен быть преднамеренно опасным, чтобы сразу вносить сложность, когда вы общаетесь с объектом, так что вы вынуждены немедленно обращаться с ним должным образом, вместо того, чтобы пытаться обращаться с ним на самом объекте и скрывать все кровавые детали и затруднять чистоту. изменить основные предположения.
У вас должно появиться чувство угасания, если объект предлагает вам реализованные эквиваленты
open
иclose
поведение, и вы сразу же начинаете понимать, с чем имеете дело, когда видите логическое возвращаемое значение для чего-то, что, по вашему мнению, будет простой атомарной задачей.То, как вы справляетесь позже, это ваша вещь. Вы можете создать абстракцию над ним, с типом виджета
removeFromParent()
, но у вас всегда должен быть низкоуровневый запасной вариант, иначе вы, возможно, делали преждевременные предположения.Не пытайтесь сделать все просто. Нет ничего более разочаровывающего, когда вы полагаетесь на что-то, что кажется элегантным и таким невинным, только для того, чтобы осознать, что это настоящий ужас позже в самые худшие моменты.
источник
То, что вы описываете, является известным «исключением» из принципа разделения команд и запросов.
В этой статье Мартин Фаулер объясняет, как он будет угрожать такому исключению.
В вашем примере я бы рассмотрел то же исключение.
источник
Разделение команд-запросов удивительно легко понять.
Если я скажу вам, в моей системе есть команда, которая также возвращает значение из запроса, и вы говорите: «Ха! Вы нарушаете!» вы прыгаете пистолет
Нет, это не то, что запрещено.
Запрещается, когда эта команда является ЕДИНСТВЕННЫМ способом сделать этот запрос. Нет. Мне не нужно менять штат, чтобы задавать вопросы. Это не значит, что я должен закрывать глаза каждый раз, когда меняю состояние.
Не имеет значения, если я это сделаю, так как я просто собираюсь открыть их снова в любом случае. Единственная выгода от этой чрезмерной реакции заключалась в том, что разработчики не ленились и забыли включить запрос без изменения состояния.
Реальное значение настолько чертовски тонко, что ленивым людям проще сказать, что сеттеры должны вернуть пустоту. Это позволяет быть уверенным, что вы не нарушаете настоящее правило, но это чрезмерная реакция, которая может стать настоящей болью.
Свободные интерфейсы и iDSL постоянно "нарушают" эту чрезмерную реакцию. Если вы чрезмерно реагируете, вы игнорируете много силы.
Вы можете утверждать, что команда должна делать только одно, а запрос должен делать только одно. И во многих случаях это хороший момент. Кто сказал, что за командой должен следовать только один запрос? Хорошо, но это не разделение команд и запросов. Это принцип единственной ответственности.
Если посмотреть так, то стэк-поп - не странное исключение. Это единственная ответственность. Pop только нарушает разделение командного запроса, если вы забыли указать peek.
источник