Переконайтесь, що кожен клас несе лише одну відповідальність, чому?


37

Відповідно до документації Microsoft, статті статті Wikipedia SOLID або більшості ІТ-архітекторів, ми повинні гарантувати, що кожен клас несе лише одну відповідальність. Мені хотілося б знати, чому, якщо всі, здається, згодні з цим правилом, схоже, ніхто не погоджується з причинами цього правила.

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



1
У мене виникло питання, з яким ви також можете зустрітись: Яка реальна відповідальність класу?
П'єр Арло

3
Чи не можуть бути всі причини одноосібної відповідальності правильними? Це полегшує обслуговування. Це полегшує тестування. Це робить клас більш міцним (загалом).
Мартін Йорк

2
З цієї ж причини у вас є заняття ВСЕ.
Давор Ждрало

2
У мене завжди були проблеми з цим твердженням. Дуже важко визначити, що таке "єдина відповідальність". Одиночна відповідальність може охоплювати будь-яке місце, починаючи від "підтвердження, що 1 + 1 = 2", до "ведення точного журналу всіх грошей, внесених на рахунки корпоративних банків та з них".
Дейв Най

Відповіді:


57

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

SRP так само застосовно до функцій, як і для класів, але основні мови OOP відносно погані при склеюванні функцій.


12
+1 для згадування функціонального програмування як більш безпечного способу складання абстракцій.
logc

> але основні мови OOP відносно погані при склеюванні функцій. <Можливо, через те, що мови OOP стосуються не функцій, а повідомлень.
Джефф Хаббард

@JeffHubbard Я думаю, ти маєш на увазі, що вони приймають після C / C ++. Немає нічого про об'єкти, що робить їх взаємовиключними з функціями. Пекло, об'єкт / інтерфейс - це лише запис / структура функцій. Прикидатися, що функції не важливі, це не обгрунтовано і в теорії, і в практиці. Їх ні в якому разі не уникнути - вам, нарешті, доведеться обміняти "Runnables", "Фабрики", "Постачальники" та "Дії" або скористатися так званими "шаблонами" стратегії та шаблону, і в кінцевому підсумку купу непотрібної котлоагрегату, щоб виконати ту саму роботу.
Доваль

@Doval Ні, я дійсно маю на увазі, що OOP переймається повідомленнями. Це не означає, що дана мова є кращою або гіршою, ніж функціональна мова програмування при склеюванні функцій разом - просто, що це не головна проблема мови .
Джефф Хаббард

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

30

Краще обслуговування, легке тестування, швидше виправлення помилок - це лише (дуже приємні) результати застосування SRP. Основна причина (як зазначає Роберт К. Матін):

Клас повинен мати одну, і лише одну, причину для зміни.

Іншими словами, SRP піднімає зміни місцевості .

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

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

1
+1 Для прив’язки SRP до SUY.
Махді

21

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

Щодо правильного: усі три. Всі вони переваги використання одноосібної відповідальності та причина, що ви повинні ним скористатися.

Що вони означають:

  • Краще обслуговування означає, що його легше змінити і не змінювати так часто. Оскільки код менше, і цей код орієнтований на щось конкретне, якщо вам потрібно змінити щось, що не пов'язане з класом, клас не потрібно змінювати. Крім того, коли вам потрібно змінити клас, до тих пір, поки вам не потрібно буде змінювати загальнодоступний інтерфейс, вам потрібно лише турбуватися про цей клас і нічого іншого.
  • Просте тестування означає менше тестів, менше налаштування. У класі не так багато рухомих частин, тому кількість можливих відмов у класі менша, а тому менше випадків, які потрібно перевірити. Буде менше приватних полів / членів для налаштування.
  • Через два вище ви отримуєте клас, який менше змінюється і менше виходить з ладу, а отже, є більш надійним.

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

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


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

2
Ви робите те саме для всіх класів. Ви закінчуєте більше класів, всі відповідають SRP. Це робить структуру складнішою з точки зору з'єднань. Але простота кожного класу це виправдовує. Мені подобається відповідь @ Довала.
Міямото Акіра

Я думаю, що ви повинні додати це до своєї відповіді.

4

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

  • Краще обслуговування : в ідеалі щоразу, коли функціональність системи повинна змінюватися, буде один і лише один клас, який потрібно змінити. Чітке відображення між класами та обов'язками означає, що будь-який розробник, який бере участь у проекті, може визначити, для кого це клас. (Як зазначав @ MaciejChałapuk, див. Роберт К. Мартін, "Чистий код" .)

  • Простіше тестування : в ідеалі класи повинні мати якомога менший публічний інтерфейс, а тести повинні стосуватися лише цього публічного інтерфейсу. Якщо ви не можете перевірити чітко, оскільки багато частин вашого класу є приватними, то це явна ознака того, що ваш клас має занадто багато обов'язків, і що ви повинні розділити його на менші підкласи. Зверніть увагу, це стосується також мов, де немає членів класу "загальнодоступні" чи "приватні"; наявність невеликого публічного інтерфейсу означає, що клієнтському коду дуже зрозуміло, які частини класу він повинен використовувати. (Див. Кент Бек, "Тестова розробка" для отримання більш детальної інформації.)

  • Надійний код : ваш код не вийде з ладу більш-менш часто, тому що він добре написаний; але, як і всі коди, його кінцева мета - не спілкуватися з машиною , а з іншими розробниками (див. Кент Бек, "Шаблони впровадження" , глава 1.) Простіше зрозуміти чітку базу коду, тому буде введено менше помилок , і між виявленням помилки та її виправленням пройде менше часу.


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

@ B413: Я не мав на увазі, що будь-яка зміна буде означати одну зміну в одному класі. У багатьох частинах системи можуть знадобитися зміни, щоб відповідати одній зміні бізнесу. Але, можливо, у вас є пункт, і я повинен був написати "щоразу, коли функціональність системи повинна змінюватися".
logc

3

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

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

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

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

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


2

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

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


1

Я дотримуюся за цю думку: 1 Class = 1 Job.

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

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

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

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


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

Так, у цьому випадку Легені є менеджером, хоча Віллі був би наглядачем, і процес Дихання був би класом Робітника для передачі частинок повітря до потоку крові.
GoldBishop

@GoldBishop: Можливо, правильним поглядом було б сказати, що Personклас виконує свою функцію делегування всіх функціональних можливостей, пов'язаних з жахливо над складною сутністю "людини-реального світу", включаючи об'єднання різних її частин . Бути суб'єктом господарювання 500 речей некрасиво, але якщо так працює реальна система, що моделюється, мати клас, який делегує 500 функцій, зберігаючи одну ідентичність, може бути кращим, ніж робити все з нерозбірливими елементами.
supercat

@supercat Або ви можете просто розділити всю цю справу та мати 1 клас із необхідними керівниками над підпроцесами. Таким чином, ви могли б (теоретично) кожен процес відокремити від базового класу, Personале все-таки звітувати по ланцюгу про успіх / невдачу, але не обов'язково впливати на інший. 500 функцій (хоч і є прикладом, але це буде за бортом і непідтримується), я намагаюся зберегти такий тип функціональних похідних та модульних.
GoldBishop

@GoldBishop: Я хотів би, щоб був спосіб декларувати "ефемерний" тип, щоб зовнішній код міг викликати на нього членів або передати його як параметр власним методам, але нічого іншого. З точки зору організації коду, підрозділення класів має сенс і навіть використання методів зовнішнього коду виклику на один рівень вниз (наприклад, Fred.vision.lookAt(George)має сенс, але дозволяти коду говорити someEyes = Fred.vision; someTubes = Fred.digestionздається прискіпливим, оскільки це затьмарює зв'язок між someEyesі someTubes.
supercat

1

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

Деякі з цих причин можуть бути:

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

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


... test is not affected by other responsibilities the class has, ви можете, будь ласка, детальніше зупинитися на цьому?
Махді

1

Найкращий спосіб зрозуміти важливість цих принципів - це мати потребу.

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

На малих проектах це може не мати значення, але у великих проектах речі можуть бути дуже кошмарними. Пізніше, коли я зіткнувся з поняттями дизайнерських малюнків, я сказав собі: "О, так, це зробило б все набагато простіше".

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

Однак, як і ви, я все ще не впевнений у "легкому тестуванні", тому що мені ще не довелося брати участь у тестуванні одиниць.


Шаблони якимось чином пов'язані з SRP, але SRP це не потрібно, коли застосовується дизайнерські шаблони. Можна використовувати шаблони і повністю ігнорувати SRP. Ми використовуємо шаблони для вирішення нефункціональних вимог, але використання шаблонів не потрібно в жодній програмі. Принципи є важливими для того, щоб зробити розвиток менш болючим, і (ІМО) має бути звичкою.
Мацей Чалапук

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

1

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

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

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

Розширений обсяг = більша складність = більше способів, щоб справи пішли не так.


1

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

Можливо, для класу є більше ніж одна відповідальність без проблем, але незабаром ви потрапляєте на проблеми із занадто складними класами.

Однак наявність простого правила "лише одна відповідальність" просто полегшує розуміння, коли вам потрібен новий клас .

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

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