Борьба с принципом единой ответственности

11

Рассмотрим этот пример:

У меня есть сайт. Это позволяет пользователям создавать сообщения (может быть что угодно) и добавлять теги, которые описывают сообщение. В коде у меня есть два класса, которые представляют пост и теги. Давайте назовем эти классы Postи Tag.

Postзаботится о создании сообщений, удалении сообщений, обновлении сообщений и т. д. Tagзаботится о создании тегов, удалении тегов, обновлении тегов и т. д.

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

С одной стороны, у Postкласса может быть функция, которая принимает Tagпараметр в качестве параметра, а затем сохраняет его в списке тегов. С другой стороны, Tagкласс может иметь функцию, которая принимает Postпараметр в качестве параметра и связывает Tagего с Post.

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

Может быть, это правильное решение для обоих классов?

Злые птицы
источник

Ответы:

11

Как и Пиратский кодекс, SRP является скорее ориентиром, чем правилом, и даже не особенно хорошо сформулированным. Большинство разработчиков приняли переопределения Мартина Фаулера (в Refactoring ) и Роберта Мартина (в Clean Code ), предполагая, что у класса должна быть только одна причина для изменения (в отличие от одной ответственности).

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

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

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

// C-style-language pseudo-code
class Post {
    string _title;
    string _content;
    Date _date;
    List<Tag> _tags;

    Post(string title, string content) {
        _title = title;
        _content = content;
        _date = Now;
        _tags = new List<Tag>();
    }

    Tag[] getTags() {
        return _tags.toArray();
    }

    void addTag(Tag tag) {
        if (_tags.contains(tag)) {
            throw "Cannot add tag twice";
        }

        _tags.Add(tag);
        tag.referencePost(this);
    }

    // more stuff here, obviously
}

class Tag {
    string _name;
    List<Post> _posts;

    Tag(string name) {
        _name = name;
    }

    Post[] getPosts() {
        return _posts.toArray();
    }

    void referencePost(Post post) {
        if (!post.getTags().contains(this) || _posts.contains(post)) {
            throw "Only reference a post by calling Post.addTag()";
        }

        _posts.Add(post);
    }

    // more stuff here too
}

Если позже вам также понадобится добавить сообщения в теги, просто добавьте метод addPost в класс Tag и метод referenceTag в класс Post. Очевидно, я назвал их по-разному, чтобы случайно не вызвать переполнение стека, вызвав addTag из addPost и addPost из addTag.

прецизионный самописец
источник
Я думаю, что отношения между тегом и сообщением многие-ко-многим, и в этом случае тег имеет смысл сохранять ссылки на несколько сообщений. Как бы вы справились с этим, если бы вы держали одну ссылку?
Андрес Ф.
@AndresF .: Я согласен с вами, поэтому я не очень хорошо написал свой ответ. Я значительно отредактировал. (Прошу прощения у предыдущего upvoter, если это меняет значение, как вы видели.)
pdr
6

Нет, не в обоих! Это должно быть в одном месте.

То, что я нахожу неловким в вашем вопросе, это то, что вы говорите: « Postзаботится о создании постов, удалении постов, обновлении постов» и то же для Tag. Ну, это не правильно. Postможно только позаботиться об обновлении, то же самое для Tag. Создание и удаление - это работа кого-то другого, вне Postи Tag(давайте просто назовем это Store).

Хорошая ответственность за Postэто "знает его автора, содержание и дату последнего обновления". Хорошая ответственность за Tagэто «знает свое имя и цель (читай: описание)». Хорошая ответственность за Storeэто "знает все сообщения и все теги и может добавлять, удалять и искать их".

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

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

Херби
источник
Если у каждого сообщения есть список тегов и / или у каждого тега есть список сообщений, в которые он включен, можно легко ответить на вопрос «действительно ли сообщение x включает тег y». Есть ли способ эффективно ответить на такой вопрос, не взяв на себя ответственность ни одного класса, кроме использования чего-то вроде ConditionalWeakTable(если предположить, что кому-то повезло, что у него есть структура, в которой он существует)?
суперкат
3

Theres важная деталь отсутствует в уравнении. Почему тег содержит почту и наоборот? Ответ на этот вопрос определяет решение для каждого данного набора.

В общем, я могу думать о ситуации, которая похожа. Коробка и содержимое. У блока есть содержимое, поэтому отношение has-a уместно. Может ли содержимое иметь коробку? Конечно, коробка с коробкой. Коробка с содержимым. Но IS-A - это не хороший дизайн для всех коробок. В таком случае я бы рассмотрел шаблон декоратора. Таким образом, коробка по мере необходимости украшается содержимым во время выполнения.

Теги тоже могут иметь сообщения, но для меня это не статичные отношения. Скорее это может быть отчет обо всех сообщениях, имеющих указанный тег. В этом случае это новая сущность, а не has-a.

P.Brian.Mackey
источник
2

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

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

Карл Билефельдт
источник
1

Лично я бы не добавил эту функциональность ни к одному из них.

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

Вместо этого у меня был бы другой класс, отвечающий за вашу бизнес-логику и данные, относящиеся к вашей веб-странице. Если ваша страница отображает сообщение и позволяет пользователям добавлять теги, то у класса будет Postобъект и функции, которые можно добавить Tagsк нему Post. Если ваша страница отображает теги и позволяет пользователям добавлять сообщения в эти теги, она будет содержать Tagобъект и иметь возможность добавлять Postsк нему Tag.

Это только я, хотя. Если вы чувствуете, что должны обрабатывать функциональность базы данных в ваших объектах данных, я бы порекомендовал ответ pdr

Рейчел
источник
0

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

С точки зрения публикации ...
Если ваше редактирование или создание публикации вторичным действием становится управление отношениями тегов .

  1. Добавить существующий тег в сообщение
  2. Revove Tag из поста

ИМО, создание совершенно нового TAG здесь не относится.

С точки зрения тегов ...
Вы можете создать тег, не назначая его для публикации. Единственное, что я вижу, это взаимодействие с постом, это функция удаления тега. Однако эта функция должна быть независимой автономной функцией.

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

Майкл Райли - AKA Gunny
источник