Чи порушує цей дизайн класу принцип єдиної відповідальності?


63

Сьогодні я мав сварку з кимось.

Я пояснював переваги створення багатої доменної моделі на відміну від анемічної доменної моделі. І я продемонстрував свою думку простим класом, який виглядає так:

public class Employee
{
    public Employee(string firstName, string lastName)
    {
        FirstName = firstName;
        LastName = lastname;
    }

    public string FirstName { get private set; }
    public string LastName { get; private set;}
    public int CountPaidDaysOffGranted { get; private set;}

    public void AddPaidDaysOffGranted(int numberOfdays)
    {
        // Do stuff
    }
}

Коли він захищав свій анемічний модельний підхід, одним із його аргументів було: "Я вірую в SOLID . Ви порушуєте принцип єдиної відповідальності (SRP), оскільки ви обидва представляєте дані та виконуєте логіку в одному класі".

Мені ця претензія виявилася дійсно дивною, оскільки слідуючи цим міркуванням, будь-який клас, що має одну властивість і один метод, порушує SRP, і тому OOP взагалі не є SOLID, а функціональне програмування - єдиний шлях до неба.

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

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

Що б ти сказав?

Оновлення: приклад був щедро оновлений Guntbert, щоб зробити метод більш реалістичним та допомогти нам зосередитись на основній дискусії.


19
цей клас порушує SRP не тому, що він змішує дані з логікою, а тому, що він має низьку згуртованість - потенційний об'єкт Бога
gnat

1
Яка мета - додавання відпусток працівникові? Має бути, можливо, календарний клас чи щось, що має свята. Я думаю, що твій друг має рацію.
Джеймс Блек

9
Ніколи не слухайте когось, хто каже "Я віруюча в X".
Стиг Хеммер

29
Справа не стільки в тому, чи є це порушення СРП, скільки в тому, чи добре це моделювання взагалі. Припустимо, я працівник. Коли я запитую свого менеджера, чи з ним нормально, якщо я їду на лижі на довгі вихідні, мій менеджер не додає мені канікул . Код тут не відповідає дійсності, яку він має намір моделювати, а тому є підозрілим.
Ерік Ліпперт

2
+1 - функціональне програмування - єдиний шлях до неба.
erip

Відповіді:


68

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

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


Якщо об'єктно-орієнтоване програмування було створено і призначене для моделювання об'єктів і класів реального життя (дії + атрибути), то чому б не написати клас коду, що має декілька обов'язків (дій)? Об'єкт реального світу може мати кілька обов'язків. Наприклад, журналіст пише редакційні публікації у газетних газетах та інтерв'ю політиків у телешоу. Дві відповідальності за об’єкт реального життя! Що робити, якщо я збираюся написати клас журналіста?
user1451111

41

Принцип єдиної відповідальності стосується лише того, чи частина фрагмента коду (в OOP, як правило, ми говоримо про класи) несе відповідальність за один функціонал . Я думаю, що твій друг сказав, що функції та дані не можуть поєднуватися, насправді не зрозумів цю ідею. Якби Employeeтакож містилася інформація про його робоче місце, про те, як швидко їздить його машина, і який тип їжі їсть його собака, то у нас виникли б проблеми.

Оскільки цей клас має справу лише з Employee, я думаю, що справедливо сказати, що він не порушує SRP відверто, але люди завжди мають свою власну думку.

Одне місце, де ми можемо покращити, - це відокремити інформацію працівника (наприклад, ім’я, номер телефону, електронну пошту) від його відпустки. На мій погляд, це не означає, що методи та дані не можуть поєднуватися між собою , це просто означає, що, можливо, функціонал відпустки може бути в окремому місці.


20

На мій погляд, цей клас потенційно може порушити SRP, якби він продовжував представляти і і, Employeeі EmployeeHolidays.

Як воно є, і якби воно дійшло до мене для експертної оцінки, я, мабуть, пропустив би це. Якщо було додано більше властивостей та методів для працівника та додано більше властивостей, пов’язаних з відпусткою, я, мабуть, порадив би розкол, посилаючись на SRP та ISP.


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

1
@NiklasH Погодився. Особисто я б не дивився випадковим чином і намагався відгадати клас, у якому я б шукав у студії слово "Відпустка" і
дивився,

4
Правда. Але що робити, якщо в цій новій системі його не називають «Відпустка», а є «Відпустка» або «Вільний час». Але я згоден, з тим, що ти, як правило, маєш змогу просто його шукати, або ти можеш попросити колегу. Моя зауваження полягала насамперед про те, щоб ОП подумки моделювати відповідальність, і де найбільш очевидним місцем для логіки було б :-)
Ніклас Х

Я згоден з вашим першим твердженням. Однак, якби справа доходила до експертного огляду, я не думаю, що це робити, тому що порушення SRP - це слизький схил, і це може бути першим з багатьох зламаних вікон. Ура.
Джим спікер

20

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

Перші два букви SOLID, SRP та OCP - це те, як змінюється ваш код у відповідь на зміну вимог. Моє улюблене визначення SRP таке: "модуль / клас / функція повинні мати лише одну причину для зміни". Сперечатися про ймовірні причини зміни коду набагато продуктивніше, ніж сперечатися про те, чи ваш код ТВОРЧИЙ чи ні.

Скільки причин має змінити клас вашого працівника? Я не знаю, бо не знаю контексту, в якому ви його використовуєте, і я також не бачу майбутнього. Що я можу зробити - це мозкові штурми можливих змін, виходячи з того, що я бачив у минулому, і ви можете суб'єктивно оцінити, наскільки вони ймовірні. Якщо декілька балів між "досить вірогідним" та "моїм кодом вже змінилися саме з цієї причини", ви порушуєте SRP проти подібних змін. Ось один: хтось із більш ніж двома іменами приєднується до вашої компанії (або архітектор читає цю чудову статтю W3C ). Ось ще: ваша компанія змінює спосіб розподілу святкових днів.

Зауважте, що ці причини однаково справедливі, навіть якщо ви видалите метод AddHolidays. Багато моделей анемічних доменів порушують SRP. Багато з них є лише таблицями баз даних у коді, і для таблиць баз даних дуже часто виникає 20+ причин.

Ось що слід жувати: чи змінився б ваш клас працівника, якби вашій системі потрібно було відстежувати зарплати працівників? Адреси? Контактна інформація про надзвичайні ситуації? Якщо ви сказали "так" (і "ймовірно, що трапиться") двом із них, то ваш клас порушив би SRP, навіть якщо в ньому ще не було коду! SOLID - це стільки процесів, скільки архітектура.


9

Те, що клас представляє дані, не є відповідальністю класу, це приватна деталь реалізації.

Клас несе одну відповідальність - представляти працівника. У цьому контексті це означає, що він представляє деякий публічний API, який надає вам функціонал, необхідний для роботи з працівниками (чи хороший приклад AddHolidays є дискусійним).

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


Цікава лінія роздумів, велике спасибі за спільний доступ
tobiak777

Хороший - хороший спосіб виразити намічені цілі ООП.
user949300

5

Думка про те, що будь-яке змішування логіки та даних завжди є помилковою, настільки смішна, що навіть не заслуговує на обговорення. Однак дійсно є чітке порушення єдиної відповідальності в прикладі, але це не тому, що є властивість DaysOfHolidaysі функція AddHolidays(int).

Це тому, що особистість працівника змішується з керівництвом відпусток, що погано. Ідентифікація працівника - це складна річ, яка потрібна для відстеження відпусток, зарплати, понаднормових робіт, представлення того, хто є в якій команді, посилання на звіти про результати роботи тощо. працівник. Співробітники можуть навіть мати декілька написань свого імені, наприклад, ASCII та правопис Unicode. Люди можуть мати від 0 до n імен та / або прізвищ. Вони можуть мати різні назви в різних юрисдикціях. Відстеження особи працівника є достатньою відповідальністю за те, що керівництво відпусткою чи відпустками не може бути додане зверху, не називаючи це другою відповідальністю.


"Відстеження особи працівника є достатньою відповідальністю за те, що керівництво відпусткою чи відпустками не може бути додане зверху, не називаючи це другою відповідальністю". + Працівник може мати декілька імен тощо. Сенс моделі полягає в тому, щоб зосередити увагу на реальних фактах реального світу для проблеми. Існують вимоги, щодо яких ця модель є оптимальною. У цих вимогах працівники цікаві лише тим, що ми можемо модифікувати їхні відпустки, і ми не дуже зацікавлені в управлінні іншими аспектами їх реального життя.
tobiak777

@reddy "Співробітники цікаві лише настільки, що ми можемо змінити їхні відпустки" - Це означає, що вам потрібно їх правильно ідентифікувати. Як тільки у вас є працівник, вони можуть змінити своє прізвище в будь-який час через шлюб або розлучення. Вони також можуть змінити своє ім’я та стать. Чи будете звільняти працівників, якщо їх прізвище зміниться таким чином, що їх ім’я відповідає прізвищу іншого працівника? Зараз ви не будете додавати всю цю функціональність. Натомість ви додасте його, коли вам це потрібно, що добре. Незалежно від того, наскільки реалізовано, відповідальність за ідентифікацію залишається однаковою.
Петро

3

"Я вірую в SOLID. Ви порушуєте принцип єдиної відповідальності (SRP), оскільки ви одночасно представляєте дані та виконуєте логіку в одному класі".

Як і інші, я не згоден з цим.

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


Немає! SRP не порушується безліччю логіки, множиною даних або будь-якою їх комбінацією. Єдина вимога - об’єкт повинен дотримуватися свого призначення. Її мета потенційно може спричинити за собою багато операцій.
Мартін Маат

@MartinMaat: Багато операцій, так. Як результат, одна логіка. Я думаю, що ми говоримо те саме, але з різними термінами (і я радий припустити, що ваші - це правильні, оскільки я цього не вивчаю часто)
Гонки легкості в орбіті

2

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

Принцип мінімального горя: код повинен або прагнути мінімізувати його ймовірність вимагати змін, або максимально легко змінити.

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

Що стосується ймовірності вимагати змін, це знижується з:

  1. Хороше тестування (підвищена надійність).
  2. Залучення лише самого мінімального коду, необхідного для виконання чогось конкретного (це може включати зменшення аферентних з’єднань).
  3. Просто створення кодового коду в тому, що він робить (див. Принцип дії бадаса).

Що стосується труднощів із внесенням змін, то це стосується еферентних з'єднань. Тестування вводить ефективні муфти, але це підвищує надійність. Зроблено добре, він, як правило, приносить більше користі, ніж шкоди, і цілком прийнятний і пропагується Принципом мінімального горя.

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

І Принцип Make Badass прив’язаний до Принципу мінімальної скорботи, оскільки речі з badass виявляють меншу ймовірність вимагати змін, ніж речі, які засмоктують те, що вони роблять.

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

З точки зору SRP, клас, який ледве робить щось, неодмінно мав би лише одну (іноді нульову) причину змінити:

class Float
{
public:
    explicit Float(float val);
    float get() const;
    void set(float new_val);
};

Це практично не має причин змінюватися! Це краще, ніж SRP. Це ZRP!

За винятком я б припустив, що це є кричущим порушенням принципу Make Badass. Це також абсолютно нікчемно. Те, що робить так мало, не може сподіватися, що воно буде поганим. У ньому занадто мало інформації (TLI). І природно, коли у вас є щось, що є TLI, воно не може зробити нічого по-справжньому значущого, навіть з інформацією, яку він інкапсулює, тому йому доведеться просочити його до навколишнього світу, сподіваючись, що хтось ще насправді зробить щось значиме і недобре. І ця протікання нормально для чогось, що призначене лише для агрегування даних, і нічого іншого, але цей поріг - це різниця, як я бачу, між "даними" та "об'єктами".

Звичайно, те, що є TMI, також погано. Ми можемо поставити все наше програмне забезпечення в один клас. Це навіть може бути лише один runметод. А хтось навіть може заперечити, що зараз є одна дуже широка причина для зміни: "Цей клас потрібно буде змінити лише в тому випадку, коли програмне забезпечення потребує вдосконалення". Я дурний, але, звичайно, ми можемо уявити всі проблеми з технічним обслуговуванням.

Таким чином, існує ефект врівноваження щодо деталізації або грубості об'єктів, які ви проектуєте. Я часто суджу про це за тим, скільки інформації ви маєте просочуватися до навколишнього світу і скільки значущої функціональності вона може виконувати. Я часто вважаю Принцип Make Badass корисним там, щоб знайти баланс, поєднуючи його з Принципом мінімального горя.


1

Навпаки, для мене анемічна модель домену порушує деякі основні поняття OOP (які поєднують між собою атрибути та поведінку), але може бути неминучим на основі архітектурного вибору. Анемічні домени легше мислити, менш органічні та послідовніші.

Багато систем, як правило, роблять це, коли кілька шарів повинні грати з одними і тими ж даними (сервісний рівень, веб-шар, клієнтський рівень, агенти ...).

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

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

Ви можете занадто легко серіалізувати / десериалізувати дані "об'єкта", або лише деякі з них, або в інший формат (json) ... не турбуючись про будь-яку концепцію / відповідальність об'єкта. Це лише передача даних. Ви завжди можете проводити відображення даних між двома класами (Співробітник, СпівробітникVO, Співробітник…), але що насправді означає працівник тут?

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

Якщо вам не потрібно розділяти сфери поведінки, ви можете вільніше розміщувати методи в класах обслуговування або в доменних класах, залежно від того, як ви бачите свій об'єкт. Я схильний розглядати об'єкт як "реальну" концепцію, це, природно, допомагає зберегти SRP. Отже, у вашому прикладі, це реалістичніше, ніж босс співробітника додає виплату, яка надається на його рахунок PayDayAccount. Працівника наймає компанія, Роботи, він може бути хворим або запитати його про пораду, і він має обліковий запис Payday (бос може отримати його безпосередньо у нього або з реєстру PayDayAccount ...) Але ви можете зробити сукупний ярлик тут для простоти, якщо ви не хочете занадто складних для простого програмного забезпечення.


Дякуємо за Ваш внесок, Вінс. Справа в тому, що вам не потрібен рівень обслуговування, коли у вас є багатий домен. Існує лише один клас, який відповідає за поведінку, і це ваш домен. Інші шари (веб-шар, користувальницький інтерфейс тощо ...) зазвичай мають справу з DTO, ViewModels, і це добре. Модель домену полягає в моделюванні домену, а не в роботі інтерфейсу або надсиланні повідомлень через Інтернет. Ваше повідомлення відображає цю поширену помилку, яка випливає з того, що люди просто не знають, як вписати OOP у свій дизайн. І я думаю, що це дуже сумно - для них.
tobiak777

Я не думаю, що я маю помилкове уявлення про багатий домен OOP, я створив багато програмного забезпечення таким чином, і це правда, це дійсно добре для обслуговування / еволюції. Але мені шкода, що я вам кажу, що це не срібна куля.
Вінс

Я не кажу, що це так. Для написання компілятора це, мабуть, не так, але для більшості напрямків бізнесу / SaaS-програм я думаю, що це набагато менше мистецтва / науки, ніж те, що ви пропонуєте. Необхідність доменної моделі можна довести математично, ваші приклади змушують мене думати про дискусійні конструкції, а не про недоліки обмежень в OOP
tobiak777

0

Ви порушуєте принцип єдиної відповідальності (SRP), оскільки представляєте дані та виконуєте логіку в одному класі.

Для мене це звучить дуже розумно. Модель може не мати публічних властивостей, якщо виставляє дії. Це в основному ідея розділення команд-запитів. Зверніть увагу, що у Команди буде точно приватний стан.


0

Ви не можете порушити Принцип єдиної відповідальності, оскільки це лише естетичний критерій, а не правило природи. Не введіть в оману науково обгрунтовану назву та великі літери.


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