Скажите, не спрашивайте и единоличную ответственность - делайте новые вещи с данными в классе

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

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

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

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

Ответы

Ответ 1

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

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

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

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

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

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

Итак, если мы объединим Tell, Do not Ask with LoD, тогда вполне нормально передавать объекты другому объекту для "запроса". Значение. У вас есть объект Analysis, который вы "Расскажите", чтобы сделать что-то, передав объект DataSet в качестве парметера. Это придерживается TDA. Внутри объекта объекта Analysis вы придерживаетесь LoD, получая доступ только к данным "близкого друга".

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

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

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

Дальнейшая ссылка здесь:

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

EDIT:

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

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

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

Ответ 2

Я бы создал объект 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 сообщает анализатору, какой ввод использовать и возвращает отчет.

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

Ответ 3

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

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

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

Ответ 4

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

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

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

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