Говорите, не спрашивайте и единственная ответственность - делать что-то новое с данными в классе

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

У меня есть программа, которая читает и обрабатывает коллекции данных из различных источников. Я создал класс для хранения и управления данными (класс «DataSet»). Он включает методы для выполнения различных операций с наборами данных, таких как сравнение двух наборов данных для создания нового, содержащего различия, и запись наборов данных в файл.

Теперь я хочу провести некоторый анализ набора данных и вывести результаты в отчет. Моя первая попытка кодирования это опрашивает набор данных, чтобы извлечь из него информацию, а затем составляет отчет, но это, похоже, противоречит принципу «Говори, не спрашивай». Итак: следует ли мне поместить методы анализа в класс DataSet и указать набору данных, чтобы он проанализировал сам себя и сгенерировал отчет? Нарушает ли это принцип единой ответственности? Что, если я захочу выполнить другие типы анализа в будущем - класс DataSet может сильно раздуться множеством различных процедур анализа, которые не имеют ничего общего с его основным назначением.

Может ли кто-нибудь предложить здесь лучший подход? Есть ли конкретный шаблон проектирования, который решает эту проблему?


person Bob    schedule 22.05.2012    source источник
comment
Привет боб! Можете ли вы опубликовать несколько примеров, чтобы лучше понять описываемый вами сценарий?   -  person nick2083    schedule 01.07.2012
comment
Приведите пример того, какие данные потребуются для вашего анализа.   -  person Weltschmerz    schedule 05.06.2014


Ответы (4)


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

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

В этом случае «Скажи, не спрашивай» работает с другими принципами, такими как Закон Деметры (который, несмотря на свое название, все еще принцип в отношении программного обеспечения, и его лучше описать как принцип наименьшего знания).

LoD сообщает нам, что метод объекта должен вызывать только другие методы.

  • На себе
  • Об объектах, переданных в него в качестве параметра
  • Любые объекты, созданные / экземпляры с объектом, переданным параметром
  • объекты направляют объекты компонентов
  • Или глобальная переменная

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

Итак, если мы объединим «Скажи, не спрашивай» с LoD, тогда совершенно нормально передавать объекты другому объекту для «запроса». Это означает, что у вас есть объект анализа, которому вы «говорите» что-то делать, передавая объект DataSet в качестве параметра. Это придерживается TDA. Внутри метода объекта Analysis вы придерживаетесь LoD, обращаясь только к данным «близких друзей».

Это также соответствует SRP, поскольку ваш DataSet по-прежнему является просто DataSet, а ваш объект Analysis является объектом Analysis.

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

Цель TDA состоит в том, чтобы ваш родительский код не запрашивал ваш DataSet о его состоянии, а затем принимал решения на основе этого. Вместо этого он должен передавать объекты другим объектам и заставлять эти объекты выполнять свои обязанности, которые могут включать в себя запросы этих объектов на предмет их состояния, но это нормально, потому что это в контексте их ответственности.

Дополнительная ссылка здесь:

http://pragprog.com/articles/tell-dont-ask

РЕДАКТИРОВАТЬ:

Если вам нужен более авторитетный источник, нет никого лучше, чем сам Мартин Фаулер (прочтите в конце, вы найдете этот комментарий)

http://martinfowler.com/bliki/TellDontAsk.html

Но лично я не использую «скажи, не спрашивай». Я стараюсь объединить данные и поведение, что часто приводит к схожим результатам. Одна вещь, которая меня беспокоит в отношении «скажи-не-спрашивай», - это то, что я видел, как он побуждает людей становиться GetterEradicators, стремясь избавиться от всех методов запросов. Но бывают случаи, когда объекты эффективно взаимодействуют, предоставляя информацию. Хорошим примером являются объекты, которые принимают входную информацию и преобразуют ее для упрощения работы своих клиентов, например с помощью EmbeddedDocument. Я видел, как код зацикливался, говоря только о том, где подходящие методы запроса упростят задачу 1. Для меня «скажи-не-спрашивай» - это ступенька к совмещению поведения и данных, но я не считаю, что это стоит особо выделять.

person Erik Funkenbusch    schedule 05.06.2014
comment
Хотя вашему объекту Analysis сказано провести анализ, он должен будет запросить DataSet некоторые данные. Итак, вы предлагаете нарушить TDA, если это касается ответственности за объекты. Я правильно понимаю? - person Vladimir Keleshev; 07.06.2014
comment
@Halst - Нет, это не нарушает TDA. Было бы невозможно написать код, который не запрашивал бы вещи у других объектов. TDA как раз подходящее время для этого. Нецелесообразно запрашивать объект, а затем вызывать различные методы этого объекта в зависимости от его состояния, это должен решить сам объект. Прочтите ссылку, которую я дал вам в конце. - person Erik Funkenbusch; 07.06.2014
comment
@Halst - Вы запутались в том, что TDA говорит об инкапсуляции данных с действиями, которые с ними работают. Но помните, что объекты, которые вы передаете другому объекту, по сути, также являются данными. Во многих случаях это просто данные, которые уже существуют в другой форме. Таким образом, создание объекта анализа и передача ему объекта данных создает инкапсуляцию данных / функции в новом объекте анализа, который придерживается TDA. Опять же, все дело в относительности и перспективе. - person Erik Funkenbusch; 07.06.2014
comment
В конце концов, вместо того, чтобы сказать, что вы нарушите TDA, вы добавляете кучу исключений в определение ... - person Phil1970; 15.12.2016

Я бы создал объект DataAnalyzer, в обязанности которого входило бы создание отчета на основе некоторого анализа входных данных.

interface DataAnalyzer {

    public function analyze($input);

    public function report();
}

Теперь у нас есть разные виды анализа, которые нам нужны.

class AnalyzerOne implements DataAnalyzer {
    //one way of analyzing and reporting
}

class AnalyzerTwo implements DataAnalyzer {
   //other way of analyzing and reporting
}

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

class DataSet {

    private $data;

    //... other methods

    public function report(DataAnalyzer $analyzer) {
        //prepare input for the analyzer from the current state
        $analyzer->analyze($input);
        return $analyzer->report();
    }

}

В итоге клиент будет выглядеть так

$dataSet = new DataSet();
//...

$firstReport = $dataSet->report(new AnalyzerOne());
$secondReport = $dataSet->report(new AnalyzerTwo());

Таким образом, каждый объект отвечает за отдельные задачи, dataSet занимается своим делом, а анализатор отвечает за отчеты. Однако мы говорим DataSet использовать анализатор для создания отчетов. Затем DataSet сообщает анализатору, какой тип ввода использовать, и возвращает отчет.

Конечно, это не единственный способ, но в целом с таким объемом информации я думаю, что это правильная идея.

person Weltschmerz    schedule 05.06.2014
comment
Но тогда AnalyzerOne и AnalyzerTwo, скорее всего, потребуется запросить некоторые данные DataSet для выполнения своего действия. Итак, ваше решение - нарушить принцип «скажи-не-спрашивай». Я правильно понимаю? - person Vladimir Keleshev; 07.06.2014
comment
Он бы ни о чем не просил DataSet. Для работы анализатору требуются некоторые входные данные. DataSet сообщает Анализатору, что нужно работать с некоторыми входными данными. DataSet предоставляет эту информацию изнутри (класс DataSet), поэтому нет нарушения инкапсуляции. Запрос информации был бы чем-то вроде передачи всего объекта DataSet в Analyzer, а затем из Anlyzer отправлял dataSet.getThis, dataSet.getThat ... - person Weltschmerz; 07.06.2014
comment
Если у вас много анализов, то для каждого из них может потребоваться какая-то часть данных ... таким образом, $input может снова быть целыми данными, поэтому в конечном итоге не будет преимуществ от добавления report функции по сравнению с возможностью получить все данные и напрямую передать его в analyse функцию. - person Phil1970; 15.12.2016

Похоже, возможно, ViewModel - это то, что вы хотите. У вас есть Model (DataSet), который отвечает за поддержание состояния ваших данных и того, что они представляют. У вас есть View (Report), который отвечает за отображение различных фрагментов данных для пользователя, но вы хотите преобразовать DataSet в представление данных, подходящее для просмотра?

Вы можете инкапсулировать ответственность за подготовку DataSet к просмотру - например, DataSetViewModel. У него могут быть такие функции, как GetDataInReportFormat().

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

person Michael Parker    schedule 16.11.2012
comment
Думаю, вопрос не в создании отчетов, а в том, чтобы провести некоторый анализ DataSet. Если мы предположим, что для выполнения этого анализа требуется несколько переменных экземпляра, то есть 2 варианта: если вы сделаете анализ как часть DataSet, то объект подтвердит, что он скажет-не-спрашивай. Если анализ будет внешним по отношению к объекту, он не подтвердит его, а вместо этого подтвердит принцип единственной ответственности. - person Vladimir Keleshev; 05.06.2014

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

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

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

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

person Darshan Joshi    schedule 10.06.2014
comment
Наследование обычно не работает. Возможно, у вас уже есть объект одного типа, когда вы получаете его из базы данных, и если предположить, что вы используете почти все данные, не имеет большого смысла, что база данных будет иметь функцию, возвращающую дюжину похожих объектов, которые различаются только в зависимости от фактического типа. ... - person Phil1970; 15.12.2016
comment
Может быть, и нет! Здесь много предположений. Если вы хотите начать новый разговор, я предлагаю задать новый вопрос. - person Darshan Joshi; 15.12.2016