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

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

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

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

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

4 ответа

Всякий раз, когда вы разрабатываете программное обеспечение, вы всегда должны сбалансировать разные принципы, потому что многие из них конфликтуют. Например, принцип СУХОЙ (не повторяйся) часто вступает в противоречие с принципом единой ответственности, особенно когда две вещи сходны, но не совсем одинаковы.

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

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

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

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

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

Таким образом, если мы объединим Tell, Don't Ask с LoD, то вполне нормально передавать объекты другому объекту для "запроса". Это означает, что у вас есть объект Analysis, который вы "говорите" сделать что-то, передавая объект DataSet в качестве параметра. Это придерживается TDA. Внутри метода объекта "Анализ" вы придерживаетесь LoD, получая доступ только к данным "близких друзей".

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Другие вопросы по тегам