Боротьба з єдиним принципом відповідальності


11

Розглянемо цей приклад:

У мене є веб-сайт. Це дозволяє користувачам робити публікації (можуть бути будь-якими) та додавати теги, що описують публікацію. У коді я маю два класи, які представляють пост та теги. Давайте можемо називати ці класи Postта Tag.

Postпіклується про створення публікацій, видалення публікацій, оновлення дописів тощо. Tagдбає про створення тегів, видалення тегів, оновлення тегів тощо.

Є одна операція, якої немає. Зв’язування тегів до публікацій. Я борюся з тим, хто повинен зробити цю операцію. Він міг однаково добре вписуватися в будь-який клас.

З одного боку, Postклас може мати функцію, яка приймає Tagяк параметр, а потім зберігає його у списку тегів. З іншого боку, Tagклас може мати функцію, яка приймає Postяк параметр і посилає Tagна Post.

Сказане - лише приклад моєї проблеми. Я насправді натрапляю на це з кількома класами, які всі схожі. Він може однаково добре вписуватися в обидва. Окрім фактичного розміщення функціональності в обох класах, які конвенції чи стилі дизайну існують, щоб допомогти мені вирішити цю проблему. Я припускаю, що має бути щось коротше, ніж просто вибрати його?

Може бути, правильна відповідь на це в обох класах?

Відповіді:


11

Як і Піратський кодекс, СРП є скоріше керівництвом, ніж правилом, і він навіть не є дуже чітким. Більшість розробників прийняли переосмислення Мартіна Фаулера (в Refactoring ) та Роберта Мартіна (в чистому коді ), припускаючи, що клас повинен мати лише одну причину зміни (на відміну від однієї відповідальності).

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

Якщо ви можете додати повідомлення до тегу і навпаки, ви не порушили принцип єдиної відповідальності. Обидва досі мають лише одну причину зміни - якщо структура цього об'єкта змінюється. Зміна структури будь-якого з них не змінює спосіб її додавання до іншого, тому ви не додаєте до нього нової «відповідальності».

Ваше остаточне рішення дійсно має бути продиктовано функціоналом, необхідним на передній частині. Можливо, буде потреба додати тег до публікації в якийсь момент, тому зробіть щось на зразок наступного:

// 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: Я з вами згоден, тому я чітко не дуже добре написав свою відповідь. Я значно відредагував. (Вибачте за попередній провідник, якщо це змінить значення, як ви його бачили.)
Пдр

6

Ні, не в обох! Він повинен бути в одному місці.

Що я вважаю неприємним у вашому запитанні, це той факт, що ви говорите " Postбереться за створення публікацій, видалення публікацій, оновлення дописів" і те саме для Tag. Ну, це не правильно. Postможе подбати лише про оновлення, те саме для Tag. Створення та видалення - це робота когось іншого, зовнішнього Postта Tag(так просто назвемо Store).

Хороша відповідальність за те Post, що "знає свого автора, зміст та дату останнього оновлення". Хороша відповідальність за те Tag, що "знає свою назву та призначення (читайте: опис)". Хороша відповідальність за те Store, що "знає всі пости та всі теги та може додавати, видаляти та шукати їх".

Якщо ви подивитеся на цих трьох учасників, хто, природно, той, хто повинен мати знання про стосунки після тегів?

(для мене це публікація, здається, природно, що вона "знає свої теги"; зворотний пошук (усі повідомлення для тегу) здається завданням магазину; хоча я можу помилятися)


Якщо в кожній публікації є список тегів та / або кожен тег має список публікацій, до яких його включають, можна легко відповісти на питання "чи містить пост x тег у". Чи є якийсь спосіб ефективно відповісти на таке запитання, не беручи до себе жодного класу, за винятком того, як використовувати щось на кшталт ConditionalWeakTable(якщо припустити, що щастить мати рамку, де така існує)?
supercat

3

У рівнянні відсутня важлива деталь. Чому тег містить пошту та візу навпаки? Відповідь на це питання визначає рішення для кожного заданого набору.

Взагалі я можу придумати подібну ситуацію. Коробка та вміст. В коробці є вміст, тож стосунки є стосунками. Чи вміст може містити коробку? Звичайно, коробка з коробкою. Ящик - це вміст. Але IS-A не є хорошим дизайном для всіх ящиків. У такому випадку я б розглядав візерунок декораторів. Таким чином ящик прикрашається вмістом під час виконання під час виконання.

Теги можуть також мати повідомлення, але для мене це не статичні стосунки. Швидше це може бути звіт про всі повідомлення, що мають тег. У цьому випадку його нова сутність, а не має-a.


2

Хоча в теорії подібні речі можуть йти в будь-який спосіб, але на практиці, коли ви переходите до реалізації, один спосіб майже завжди краще підходить, ніж інший. Моя думка - це краще впишеться в Postклас, тому що асоціація буде створена під час створення публікацій чи редагування, коли інші речі щодо публікації змінюються одночасно.

Крім того, якщо ви пов'язуєте кілька тегів і хочете зробити це в одному оновленні бази даних, вам потрібно буде скласти якийсь список всіх тегів, пов’язаних з тим самим дописом, перш ніж оновити. Цей список вписується набагато краще в Postкласі.


1

Особисто я не додав би цю функціональність жодному з них.

Для мене, як Postі Tagоб'єкти даних, тому не слід обробка функціональності бази даних. Вони повинні просто існувати. Вони призначені для зберігання даних та використовуються в інших частинах вашої програми.

Натомість у мене буде ще один клас, який відповідає за вашу логіку бізнесу та дані, що стосуються вашої веб-сторінки. Якщо на вашій сторінці відображається публікація і дозволяється користувачам додавати теги, то клас матиме Postоб’єкт і міститиме функціональні можливості для додавання Tagsдо нього Post. Якщо на вашій сторінці відображаються теги і дозволяється користувачам додавати публікації до цих тегів, вона міститиме Tagоб'єкт і має функціональні можливості додати Postsїх Tag.

Це все-таки я. Якщо ви вважаєте, що ви повинні керувати функціональністю бази даних у ваших об'єктах даних, то я рекомендую відповідь pdr


0

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

З точки зору повідомлення ...
Якщо ви редагуєте або створюєте публікацію, вторинна діяльність стає керуванням тегів .

  1. Додати існуючий тег до публікації
  2. Скасувати тег від Пост

ІМО, створення абсолютно нового TAG тут не належить.

З точки зору тегу ...
Ви можете створити тег, не призначаючи його публікації. Єдина діяльність, яку я бачу, що стосується взаємодії з публікацією, - це функція Видалити тег. Однак ця функція повинна бути незалежною окремою функцією.

Це буде працювати лише в тому випадку, якщо є таблиця, що пов'язує базу даних, яка вирішує відносини "Багато до багатьох"

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.