Разве Информационный Эксперт и Скажи, Не спрашивай в противоречии с принципом Единой Ответственности? - PullRequest
24 голосов
/ 04 октября 2008

Information-Expert , Tell-Don't-Ask и SRP часто упоминаются вместе как лучшие практики. Но я думаю, что они расходятся. Вот о чем я говорю.

Код, который одобряет SRP, но нарушает режим «говорите, не спрашивайте» и «Info-Expert»:

Customer bob = ...;
// TransferObjectFactory has to use Customer's accessors to do its work, 
// violates Tell Don't Ask
CustomerDTO dto = TransferObjectFactory.createFrom(bob); 

Код, который поддерживает функцию «скажи, не спрашивай» и «эксперт по информации», но нарушает SRP:

Customer bob = ...;
// Now Customer is doing more than just representing the domain concept of Customer,
// violates SRP
CustomerDTO dto = bob.toDTO();

Пожалуйста, напишите мне, как эти практики могут мирно сосуществовать.

Определения терминов,

  • Информационный эксперт: объекты, содержащие данные, необходимые для операции, должны содержать операцию.

  • Скажите Не спрашивайте: не запрашивайте данные у объектов для выполнения работы; скажи объектам, чтобы сделать работу.

  • Принцип единой ответственности: каждый объект должен иметь узко определенную ответственность.

Ответы [ 6 ]

8 голосов
/ 04 октября 2008

Я не думаю, что они настолько противоречат друг другу, поскольку подчеркивают разные вещи, которые причинят вам боль. Один из них касается структурирования кода, чтобы прояснить, в чем заключаются конкретные обязанности, и уменьшения связи, а другой - уменьшения причин для изменения класса.

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

Мы создали много полезных руководств, принципов и шаблонов, которые могут помочь нам принимать решения.

Каждый из них полезен для обнаружения различных типов проблем, которые могут присутствовать в наших проектах. Для любой конкретной проблемы, на которую вы можете смотреть, где-то найдется сладкое место.

Различные руководящие принципы противоречат друг другу. Простое применение каждого руководства, которое вы услышали или прочитали, не улучшит ваш дизайн.

Для решения конкретной проблемы, с которой вы сталкиваетесь сегодня, вам необходимо решить, каковы наиболее важные факторы, которые могут причинить вам боль.

5 голосов
/ 02 июня 2010

Вы можете говорить о «Скажи, не спрашивай», когда вы спрашиваете состояние объекта, чтобы заставить объект что-то делать.

В вашем первом примере TransferObjectFactory.createFrom просто конвертер. Он не указывает объекту Customer делать что-либо после проверки его состояния.

Я думаю, что первый пример верен.

2 голосов
/ 04 октября 2008

Эти классы не расходятся. DTO просто служит каналом передачи данных из хранилища, которое предназначено для использования в качестве тупого контейнера. Это, конечно, не нарушает ПСП.

С другой стороны, метод .toDTO сомнителен - почему Заказчик должен нести эту ответственность? Ради "чистоты" у меня был бы другой класс, задачей которого было создание DTO из бизнес-объектов, таких как Customer.

Не забывайте, что эти принципы являются принципами, и когда вы можете отказаться от более простых решений до тех пор, пока изменение требований не вызовет проблемы, сделайте это. Ненужная сложность - это определенно то, чего следует избегать.

Я настоятельно рекомендую, кстати, Agile Patterns, Практики и принципы Роберта К. Мартина для более углубленного изучения этого предмета.

1 голос
/ 29 апреля 2017

Крейг Ларман обсуждал это, когда представил GRASP в разделе «Применение UML и шаблонов для объектно-ориентированного анализа, проектирования и итеративной разработки» (2004):

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

Например, кто должен нести ответственность за сохранение продажи в базе данных? Конечно, большая часть информации, которая будет сохранена, находится в объекте Sale, и, таким образом, эксперт может утверждать, что ответственность лежит на классе Sale. И, благодаря логическому расширению этого решения, каждый класс будет иметь свои собственные службы для сохранения себя в базе данных. Но действия по этим причинам приводят к проблемам сплоченности, связи и дублирования. Например, класс Sale теперь должен содержать логику, связанную с обработкой базы данных, например, связанную с SQL и JDBC (Java Database Connectivity). Класс больше не фокусируется только на чистой логике приложения «быть продажей». Теперь другие виды ответственности снижают его сплоченность. Этот класс должен быть связан со службами технической базы данных другой подсистемы, такими как службы JDBC, а не просто связан с другими объектами на уровне домена программных объектов, поэтому его связь увеличивается. И вполне вероятно, что подобная логика базы данных будет дублирована во многих постоянных классах.

Все эти проблемы указывают на нарушение основного архитектурного принципа: дизайн для разделения основных системных проблем. Храните логику приложения в одном месте (например, объекты программного обеспечения домена), сохраняйте логику базы данных в другом месте (например, в отдельной подсистеме служб персистентности) и т. Д., А не смешивайте различные системные проблемы в одном и том же компоненте. [11]

Поддержка разделения основных проблем улучшает сцепление и сплоченность конструкции. Таким образом, даже несмотря на то, что с помощью Expert мы могли найти какое-то оправдание для того, чтобы возложить ответственность за службы баз данных на класс Sale, по другим причинам (обычно сплоченности и связности) у нас получился бы плохой дизайн.

Таким образом, ПСП, как правило, превосходит Информационного Эксперта.

Однако принцип инверсии зависимости может хорошо сочетаться с экспертом. Аргументом здесь будет то, что у Клиента не должно быть зависимости CustomerDTO (от общего к подробному), а наоборот. Это будет означать, что CustomerDTO является экспертом и должен знать, как построить себя, учитывая клиента:

CustomerDTO dto = new CustomerDTO(bob);

Если у вас аллергия на новые, вы можете зайти в состояние покоя:

CustomerDTO dto = CustomerDTO.buildFor(bob);

Или, если вы ненавидите оба, мы возвращаемся к AbstractFactory:

public abstract class DTOFactory<D, E> {
    public abstract D createDTO(E entity);
}


public class CustomerDTOFactory extends DTOFactory<CustomerDTO, Customer> {
    @Override
    public CustomerDTO createDTO(Customer entity) {
        return new CustomerDTO(entity);
    }
}
1 голос
/ 16 ноября 2012

DTO с сестринским классом (как у вас) нарушают все три принципа, которые вы заявили, и инкапсуляцию, вот почему у вас возникают проблемы.

Для чего вы используете это CustomerDTO, и почему вы не можете просто использовать Customer и иметь данные DTO внутри клиента? Если вы не будете осторожны, CustomerDTO понадобится Клиент, а Клиенту потребуется CustomerDTO.

TellDontAsk говорит, что если вы основываете решение на состоянии одного объекта (например, клиента), то это решение должно выполняться внутри самого класса клиента.

Например, если вы хотите напомнить клиенту о необходимости оплаты любых неоплаченных счетов, вы звоните

  List<Bill> bills = Customer.GetOutstandingBills();
  PaymentReminder.RemindCustomer(customer, bills);

это нарушение. Вместо этого вы хотите сделать

Customer.RemindAboutOutstandingBills() 

(и, конечно, вам нужно будет передать в PaymentReminder как зависимость от конструкции клиента).

Эксперт по информации в значительной степени говорит то же самое.

Принцип единой ответственности можно легко понять неправильно - он говорит, что класс клиента должен нести одну ответственность, а также то, что ответственность за группирование данных, методов и других классов, соответствующих концепции «Клиент», должна быть заключена в один класс , Чрезвычайно сложно точно определить, что представляет собой отдельная ответственность, и я бы порекомендовал больше читать по этому вопросу.

0 голосов
/ 23 августа 2012

Я не согласен на 100% с вашими двумя примерами как с репрезентативными, но с общей точки зрения вы, кажется, исходите из предположения о двух объектах и ​​только двух объектах.

Если вы еще больше отделите проблему и создадите один (или более) специализированных объектов для выполнения своих индивидуальных обязанностей, а затем попросите управляющий объект передать экземпляры других объектов, которые он использует, в специализированные объекты, которые вы вырезали выключено, вы должны быть в состоянии наблюдать счастливый компромисс между SRP (каждая ответственность обрабатывается специализированным объектом) и Tell Don't Ask (управляющий объект сообщает специализированным объектам, которые он создает, чтобы сделать то, что они сделать друг другу).

Это композиционное решение, которое использует какой-то контроллер для координации и делегирования между другими объектами, не впадая в внутренние детали.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...