Використовуючи Принцип єдиної відповідальності, що являє собою "відповідальність?"


198

Здається, досить зрозуміло, що "Принцип єдиної відповідальності" не означає "лише одне". Ось для чого методи.

public Interface CustomerCRUD
{
    public void Create(Customer customer);
    public Customer Read(int CustomerID);
    public void Update(Customer customer);
    public void Delete(int CustomerID);
}

Боб Мартін каже, що "заняття повинні мати лише одну причину для зміни". Але це важко переконатися, якщо ви новачок у програмі SOLID.

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

То як же це зробити? Як Ви визначаєте, які обов'язки повинен мати кожен клас, і як Ви визначаєте відповідальність у контексті СРП?


28
Опублікувати на Перегляд коду та бути зірваним :-D
Йорг W Міттаг

8
@ JörgWMittag Привіт, не лякай людей :)
Flambino

117
Питання , як це від ветеранських членів демонструють , що правила і принципи , які ми спроба провести не в якому разі НЕ просто або просто . Вони [рід] суперечливий і містичні ... як будь-який хороший набір правил повинно бути. І я хотів би повірити таким питанням, як покірливий мудрий, і дати надію тим, хто почуває себе безнадійно дурним. Спасибі, Роберте!
svidgen

41
Цікаво, чи було б це питання негайно скасовано + позначений дублікат, якби його опублікував noob :)
Andrejs

9
@rmunn: або іншими словами - великий представник залучає ще більше представників, оскільки ніхто не відміняв основних забобонів людини щодо stackexchange
Andrejs

Відповіді:


117

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

Наприклад:

Нова вимога бізнесу: Користувачі, розташовані в Каліфорнії, отримують спеціальну знижку.

Приклад "хорошої" зміни: мені потрібно змінити код у класі, який обчислює знижки.

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

Або:

Нова нефункціональна вимога: ми почнемо використовувати Oracle замість SQL Server

Приклад доброї зміни: Просто потрібно змінити один клас на рівні доступу до даних, який визначає, як зберігати дані в DTO.

Погана зміна: мені потрібно змінити всі мої класи бізнес-шару, оскільки вони містять логіку, орієнтовану на SQL Server.

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

Як мінімум, ваші заняття повинні відокремлювати логічні проблеми від фізичних. Великий набір прикладів можна знайти в System.IOпросторі імен: там можна знайти різні види фізичних потоків (наприклад FileStream, MemoryStreamчи NetworkStream) і різних читачів і письменників ( BinaryWriter, TextWriter) , які працюють на логічному рівні. Поділяючи їх таким чином, ми уникаємо комбинаторного вибуху: замість того , FileStreamTextWriter, FileStreamBinaryWriter, NetworkStreamTextWriter, NetworkStreamBinaryWriter, MemoryStreamTextWriter, і MemoryStreamBinaryWriter, ви просто підключити письменник і потік , і ви можете мати те , що ви хочете. Тоді пізніше ми можемо додати, скажімо, анну XmlWriter, не потребуючи повторної реалізації для пам'яті, файлів та мережі окремо.


34
Хоча я погоджуюся з думкою вперед, існують такі принципи, як YAGNI, а такі методики, як TDD thar, пропонують протилежне.
Роберт Харві

87
YAGNI каже нам не будувати речі, які нам сьогодні не потрібні. Це не говорить не будувати речі таким чином, який можна розширити. Дивіться також принцип відкритого / закритого типу , в якому зазначено, що "програмні об'єкти (класи, модулі, функції тощо) повинні бути відкритими для розширення, але закритими для модифікації".
Джон Ву

18
@JohnW: +1 лише для вашого коментаря YAGNI Я не можу повірити, наскільки я мушу пояснити людям, що YAGNI не є приводом для побудови жорсткої, негнучкої системи, яка не може реагувати на зміни - за іронією долі, навпаки тому, до чого прагнуть принципи SRP та відкритих / закритих.
Грег Бургхардт

36
@JohnWu: Я не погоджуюся, YAGNI говорить нам точно не будувати речі, які нам сьогодні не потрібні. Наприклад, читабельність і тести - це те, що програма завжди потребує "сьогодні", тому YAGNI ніколи не є приводом для того, щоб не додавати структуру та точки введення. Однак, як тільки "розширюваність" додає значні витрати, для яких переваги не очевидні "сьогодні", YAGNI означає уникнути подібного розширення, оскільки остання призводить до переобладнання.
Док Браун

9
@JohnWu Ми перейшли з SQL 2008 на 2012 рік. Загалом було два запити, які потрібно змінити. І від SQL Auth довіреним? Чому це навіть зміна коду; зміни з'єднанняСтринг у конфігураційному файлі достатньо. Знову ЯГНІ. ЯГНІ та СРП іноді суперечать проблемам, і вам потрібно судити, хто з них має кращу вартість / вигоду.
Енді

76

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

Йдеться про те, що, на ваш досвід , ймовірно, зміниться.

Ми схильні застосовувати мову принципу в гіперболічній, буквальній, ревній люті. Ми схильні розділяти класи, тому що вони можуть змінитись, або, таким чином, просто допомагають нам вирішити проблеми. (Остання причина не є своєю суттю поганою.) Але СРП не існує заради себе; це на сервісі створення бездоганного програмного забезпечення.

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

Коли дядько Боб пише про це, він пропонує нам подумати про "відповідальність" з точки зору того, "хто просить зміни". Іншими словами, ми не хочемо, щоб Партія А втрачала роботу, оскільки Партія В попросила зміни.

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

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

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


1. Щоб бути зрозумілим, це не означає, що вони погані . Вони можуть бути чудовими підрозділами, які значно покращують читабельність коду. Це просто означає, що їх не керує СРП.


11
Найкраща відповідь і насправді цитує думки дядька Боба. Що стосується того, що, можливо, зміниться, всі роблять велику справу над введенням-виведенням, "а що, якщо ми змінимо базу даних?" або "що робити, якщо ми переходимо з XML в JSON?" Я думаю, що це зазвичай неправильно. Справжнє запитання повинно полягати в тому, "що, якщо нам потрібно змінити цей int на float, додати поле та змінити цей рядок до списку рядків?"
user949300

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

6
@Basilevs Я намагаюся відзначити той недолік, який ви бачите у цій відповіді - не кажучи вже про відповідь дядька Боба! Але, можливо, мені потрібно уточнити, що SRP не в тому, щоб "зміна" вплинула лише на 1 клас. Йдеться про те, щоб кожен клас відповідав лише на "одну зміну". ... Йдеться про спробу намалювати стрілки з кожного класу одному власнику. Не від кожного власника до одного класу.
svidgen

2
Дякую за надання прагматичної відповіді! Навіть дядько Боб застерігає від ревного дотримання принципів SOLID в Agile Architecture . У мене немає цитати під рукою, але він, в основному, каже, що розподіл обов'язків по суті збільшує рівень абстракції у вашому коді і що вся абстракція приходить дорожче, тому переконайтеся, що вигода від дотримання SRP (або інших принципів) переважає вартість. додавання більшої абстракції. (продовження наступного коментаря)
Майкл Л.

4
Ось чому ми повинні ставити продукт перед покупцем якомога раніше і настільки часто, наскільки це розумно, щоб вони змусили зміни в нашому дизайні, і ми зможемо побачити, які сфери можуть змінитися в цьому продукті. Крім того, він попереджає, що ми не можемо захистити себе від будь -яких змін. У будь-якому застосуванні певні зміни будуть важко внести. Ми повинні переконатися, що це ті зміни, які найменш вірогідні.
Майкл Л.

29

Я слідую, щоб "заняття мали лише одну причину для зміни".

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

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

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

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


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

16
Я великий прихильник ідеї "крок ліфта". Якщо важко пояснити, що робить клас у реченні або два, ти опинишся на ризикованій території.
Іван,

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

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

1
@KevinKrumwiede Саме для цього розроблені методології "Курка, що біжить з відрізаною головою" та "Погоня за дикими гусками"!

26

Ніхто не знає. Або, принаймні, ми не можемо домовитися про одне визначення. Саме це робить SPR (та інші принципи SOLID) досить суперечливими.

Я можу стверджувати, що можливість зрозуміти, що є відповідальністю, чи не є однією з навичок, які розробник програмного забезпечення повинен засвоїти протягом своєї кар'єри. Чим більше коду ви пишете та переглядаєте, тим більше досвіду вам доведеться визначити, чи є щось одне або кілька обов'язків. Або якщо одна відповідальність розбита на окремі частини коду.

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


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

9
+42 за останнім абзацом. Як говорить @RobertHarvey, такі речі, як SPR, SOLID та YAGNI, не слід сприймати як " абсолютні правила ", а як загальні принципи "гарних порад". Між ними (та іншими) порада іноді буде суперечливою, але врівноваження цих порад (на відміну від дотримання жорсткого набору правил) (з часом, у міру зростання вашого досвіду) допоможе вам виробляти краще програмне забезпечення. У розробці програмного забезпечення повинно бути лише одне "абсолютне правило": " Не існує абсолютних правил ".
TripeHound

Це дуже добре роз'яснення одного аспекту СРП. Але, навіть якщо принципи SOLID не є жорсткими правилами, вони не є надзвичайно цінними, якщо ніхто не розуміє, що вони означають - ще менше, якщо ваше твердження про те, що «ніхто не знає» насправді є правдивим! ... їм є сенс важко зрозуміти. Як і будь-яка майстерність, є щось, що відрізняє добро від менш хорошого! Але ... "ніхто не знає" робить це більше ритуалом діловодства. (І я не вірю, що це задум
СОЛІДА

3
Під "Ніхто не знає", я сподіваюся, що @Euphoric просто означає, що немає точного визначення, яке буде працювати для кожного випадку використання. Це щось, що вимагає ступеня судження. Я думаю, що один з найкращих способів визначити, де лежать ваші обов'язки - це швидко повторити і дозволити вашій кодовій базі сказати вам . Подивіться на "запахи", що ваш код нелегкий для обслуговування. Наприклад, коли зміна одного правила бізнесу починає каскадно впливати через, здавалося б, непов'язані класи, ви, ймовірно, маєте порушення SRP.
Майкл Л.

1
Я сердечно другий @TripeHound та інші, хто вказав, що всі ці "правила" не існують для визначення Єдиної справжньої релігії розвитку, а для збільшення ймовірності розробки програмного забезпечення, що підтримується. Будьте дуже обережні, дотримуйтесь "найкращої практики", якщо ви не можете пояснити, як це рекламує програмне забезпечення, покращує якість або підвищує ефективність розробки ..
Michael L.

5

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

  • Відповідальність співмірна з повноваженнями.
  • Жодна з двох організацій не повинна відповідати за одне і те ж.

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

Окрім цих двох, третім принципом здається розумним:

  • Відповідальність може бути делегована

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

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


Я не на 100% впевнений, що це повністю пояснює. Але я думаю, що пояснення "відповідальності" стосовно "авторитету" - це проникливий спосіб сформулювати це! (+1)
svidgen

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

@nocomprende Ви також схильні нарощувати свої сили в машині. Я б заперечував, що коли ваші сильні сторони і ваша слабкість - це одне і те ж, це стає цікавим.
Корт Аммон

5

На цій конференції в Єлі дядько Боб наводить цей кумедний приклад:

Введіть тут опис зображення

Він каже, що Employeeмає три причини зміни, три джерела вимог до змін, і дає це жартівливе і чітке , але все-таки ілюстративне пояснення:

  • Якщо CalcPay()метод має помилку і коштує компанії мільйони доларів США, фінансовий директор звільнить вас .

  • Якщо ReportHours()метод має помилку і коштує компанії мільйони доларів США, COO звільнить вас .

  • Якщо в WriteEmmployee(методі) виникла помилка, яка викликає стерти багато даних і коштує компанії мільйонів доларів США, CTO звільнить вас .

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

Він дає це рішення, яке вирішує порушення SRP, але все ж має вирішити порушення DIP, яке не показано на відео.

Введіть тут опис зображення


Цей приклад більше схожий на клас, який має неправильні обов'язки.
Роберт Харві

4
@RobertHarvey Коли клас має занадто багато відповідальності, це означає, що додаткові функції - це неправильна відповідальність.
Tulains Córdova

5
Я чую, що ви говорите, але не вважаю це переконливим. Існує різниця між класом, який має занадто багато обов'язків, і класом, який робить щось, у чому його взагалі немає. Це може звучати так само, але це не так; підрахунок арахісу - це не те саме, що називати їх волоськими горіхами. Це принцип дядька Боба і приклад дядька Боба, але якби він був достатньо описовим, нам це питання взагалі не знадобилося б.
Роберт Харві

@RobertHarvey, в чому різниця? Ці ситуації здаються мені ізоморфними.
Пол Дрейпер

3

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

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

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

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


Це обґрунтована порада. Можливо, варто зазначити, що ви розподіляєте відповідальність за більшістю критеріїв, ніж лише ПСП.
Jørgen Fogh

1
Аналогія автомобіля: мені не потрібно знати, скільки газу в чужому баку, або хочу вмикати чиїсь склоочисники. (але це визначення Інтернету) (Ше! ви

1
@nocomprende - "Мені не потрібно знати, скільки газу в чужому баку", - якщо ви не підліток, який намагається вирішити, яку з машин родини "позичити" для наступної поїздки ...;)
alephzero

3

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

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

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

Використовуйте занадто багато непрямих і це знову стає безладом ... як і для інших принципів, цей буде конфліктувати з іншими, такими як KISS, YAGNI тощо. Все - це питання балансу.


Хіба SRP не є великим записом згуртованості Костянтина?
Нік Кейлі

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

@ NickKeighley Я думаю, що це згуртованість, не стільки велика кількість записів, скільки дивилася з іншого погляду.
sdenham

3

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

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

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

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

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

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

Цитувати дядька Боба, який винайшов цей термін:

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

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


За його книгою "Чиста архітектура" це абсолютно правильно. Правила ведення бізнесу мають надходити з одного джерела та лише одного разу. Це означає, що HR, Операції та ІТ повинні співпрацювати, формулюючи вимоги в "Єдиній відповідальності". І в цьому полягає принцип. +1
Бенні Скогберг

2

Хороша стаття, в якій пояснюються принципи програмування SOLID та наводяться приклади коду як наступних, так і не слідуючих цим принципам, є https://scotch.io/bar-talk/solid-the-first-five-principles-of-object-oriented- дизайн .

У прикладі, що стосується SRP, він наводить приклад кількох класів форми (коло та квадрат) та класу, призначеного для обчислення загальної площі декількох фігур.

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

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

Це простий приклад (і легше зрозуміти, читаючи статтю, оскільки вона має фрагменти коду), але демонструє основну ідею SRP.


0

Перш за все, у вас є насправді дві окремі проблеми: проблема, які методи поставити у ваших класах, і проблема роздуття інтерфейсу.

Інтерфейси

У вас є цей інтерфейс:

public Interface CustomerCRUD
{
  public void Create(Customer customer);
  public Customer Read(int CustomerID);
  public void Update(Customer customer);
  public void Delete(int CustomerID);
}

Імовірно, у вас є кілька класів, які відповідають CustomerCRUDінтерфейсу (інакше інтерфейс не потрібний), а також деяка функція, do_crud(customer: CustomerCRUD)яка виконує відповідний об'єкт. Але ви вже порушили СРП: ви зв'язали ці чотири чіткі операції разом.

Скажімо, пізніше ви працювали б над переглядами бази даних. Подання бази даних має тільки на Readдоступний метод для цього. Але ви хочете записати функцію, do_query_stuff(customer: ???)яку оператори прозоро використовують або на повномасштабних таблицях, або на видах; він використовує лише Readметод, зрештою.

Тож створіть інтерфейс

публічний інтерфейс CustomerReader {публічний читач клієнта (customerID: int)}

та розподіліть свій CustomerCrudінтерфейс як:

public interface CustomerCRUD extends CustomerReader
{
  public void Create(Customer customer);
  public void Update(Customer customer);
  public void Delete(int CustomerID);
}

Але кінці не видно. Можуть бути об’єкти, які ми можемо створювати, але не оновлювати і т. Д. Цей кролячий отвір занадто глибокий. Єдиний розумний спосіб дотримуватися принципу єдиної відповідальності - зробити так, щоб усі ваші інтерфейси містили саме один метод . Перехід фактично слід за цією методологією з того, що я бачив, з переважною більшістю інтерфейсів, що містять одну функцію; якщо ви хочете вказати інтерфейс, який містить дві функції, вам доведеться незграбно створити новий інтерфейс, який поєднує ці дві. Незабаром ви отримаєте комбінаторний вибух інтерфейсів.

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

let do_customer_stuff customer = customer.read ... customer.update ...

що називає всі способи, які нам подобаються OCaml використовуватиме висновок типу, щоб визначити, що ми можемо передати будь-який об’єкт, який реалізує ці методи. У цьому прикладі було б визначити, що customerмає тип <read: int -> unit, update: int -> unit, ...>.

Заняття

Це вирішує безлад інтерфейсу ; але нам ще належить реалізувати класи, які містять кілька методів. Наприклад, чи слід створити два різні класи, CustomerReaderі CustomerWriter? Що робити, якщо ми хочемо змінити, як читаються таблиці (наприклад, зараз ми кешуємо свої відповіді в редакторі раніше після отримання даних), але тепер, як вони записуються? Якщо ви дотримуєтесь цього ланцюжка міркувань до його логічного завершення, ви нерозривно ведете до функціонального програмування :)


4
"Безглуздий" трохи сильний. Я міг би відстати від «містичного» або «Дзен». Але, а не безглузде!
svidgen

Чи можете ви пояснити трохи більше, чому структурне підтипування є рішенням?
Роберт Харві

@RobertHarvey істотно реструктурував мою відповідь
садок

4
Я використовую інтерфейси навіть тоді, коли в мене реалізується лише один клас. Чому? Знущання в одиничних тестах.
Вічний21

0

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

Потік використання буде заданим послідовним викликом методу, який дасть вам очікуваний (таким чином перевірений) результат. Ви в основному визначаєте клас із випадками використання, які отримав IMHO, тому вся методологія програми орієнтована на інтерфейси над реалізацією.


0

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

Але вдало розуміючи, що на перший погляд, коли ви вперше чуєте про SOLID.


Я бачу багато коментарів, які говорять про те, що SRP та YAGNI можуть суперечити іншим , але YAGN, який я застосовував TDD (GOOS, London School), навчив мене думати і розробляти свої компоненти з точки зору клієнта. Я почав проектувати свої інтерфейси з того, що найменше хотів би, щоб це робив кожен клієнт, тобто так мало він повинен робити . І цю вправу можна зробити без будь-яких знань TDD.

Мені подобається техніка, описана дядьком Боб (я не можу пригадати звідки, на жаль), яка йде на зразок:

Запитайте себе, що робить цей клас?

Чи відповідь містила або І, або Ор

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

Ця методика є абсолютною, і, як сказав @svidgen, SRP - це виклик судження, але коли вивчаєш щось нове, абсолютні речі найкращі, то простіше просто завжди щось робити. Переконайтесь, що причина, яку ви не розлучаєте, - це; освічена оцінка, а не тому, що ви не знаєте, як це зробити. Це мистецтво, і воно потребує досвіду.


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

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

Теоретично без SRP у вас не було б ніяких залежностей ...

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

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


3
When learning something new, absolutes are the best, it is easier to just always do something.- На мій досвід, нові програмісти є занадто догматичними. Абсолютизм призводить до розробників, які не думають, і програмують культовий культ. Говорити "просто роби це" добре, якщо ти зрозумієш, що людині, з якою ти розмовляєш, доведеться пізніше вивчити те, чого ти їх навчив.
Роберт Харві

@RobertHarvey, Цілком вірно, це створює догматичну поведінку, і вам доведеться навчатися / перенавчатися, коли ви отримуєте досвід. Однак це моя думка. Якщо новий програміст намагається робити виклики судів без будь-якого способу обґрунтовувати своє рішення, це видається кордоном марним, оскільки вони не знають, чому це спрацювало, коли воно працювало. Змушуючи людей перестаратися , це вчить шукати винятки, а не робити некваліфіковані здогадки. Все, що ви сказали, про абсолютизм є правильним, саме тому воно повинно бути лише вихідним.
Кріс Волетрт

@RobertHarvey, Короткий приклад із реального життя : Ви можете навчити своїх дітей завжди бути чесними, але, коли вони дорослішають, вони, мабуть, зрозуміють кілька винятків, коли люди не хочуть чути їх найчесніших думок. Очікувати, що 5-річний виголосить правильний дзвінок щодо сумлінності, в кращому випадку оптимістично. :)
Кріс Волетрт

0

На це немає чіткої відповіді. Хоча питання вузьке, пояснень немає.

Для мене це щось на зразок бритва Оккама, якщо ви хочете. Це ідеал, де я намагаюся порівняти свій поточний код. Це важко забити це простими і простими словами. Ще одна метафора - це «одна тема», яка є настільки ж абстрактною, тобто важкою для розуміння, як «одна відповідальність». Третім описом буде "робота з одним рівнем абстракції".

Що це означає практично?

Останнім часом я використовую стиль кодування, який складається здебільшого з двох фаз:

Першу фазу найкраще описати як творчий хаос. У цій фазі я записую код у міру течії думок - тобто сирих і негарних.

Фаза II - це повна протилежність. Це як прибирання після урагану. Для цього потрібна найбільше праці та дисципліни. А потім я дивлюся на код з точки зору дизайнера.

Зараз я працюю здебільшого в Python, що дозволяє мені мислити предмети та класи пізніше. Перша фаза I - я записую лише функції та поширюю їх майже випадковим чином у різних модулях. На II фазі , після того, як я все розібрався, я детальніше ознайомлююся з тим, який модуль має справу з якою частиною рішення. І, переглядаючи модулі, у мене виникають теми . Деякі функції пов'язані тематично. Це хороші кандидати на заняття . І після того, як я перетворив функції на класи - що майже робиться з відступом та додаванням selfдо списку параметрів у python;) - я використовую, SRPяк Occam's Razor, щоб викреслити функціональність для інших модулів та класів.

Поточним прикладом може бути написання функцій невеликого експорту днями.

Виникла потреба у форматі csv , excel та комбінованих листах excel на блискавці.

Простий функціонал виконувався в трьох видах (= функції). Кожна функція використовувала загальний метод визначення фільтрів та другий метод для отримання даних. Потім у кожній функції відбувалася підготовка експорту і була доставлена ​​у відповідь від сервера.

Занадто багато рівнів абстракції змішалося:

I) розгляд вхідного / вихідного запиту / відповіді

II) визначення фільтрів

III) отримання даних

IV) перетворення даних

Найпростішим кроком було використання однієї абстракції ( exporter) для обробки шарів II-IV на першому кроці.

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

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

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

На наступному кроці я розбив фактичні механізми експорту: Де потрібно було записувати у тимчасовий файл, я розділив це на два "обов'язки": один для фактичного запису даних на диск та інший, який стосувався фактичного формату.

У ході формування класів і модулів все стало зрозуміліше, що належало куди. І завжди латентне питання, чи робить клас занадто багато .

Як Ви визначаєте, які обов'язки повинен мати кожен клас, і як Ви визначаєте відповідальність у контексті СРП?

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

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

Уявіть мене як кодуючого Боб Росса ;)


0

Що я намагаюся зробити, щоб написати код, що відповідає SRP:

  • Виберіть конкретну проблему, яку потрібно вирішити;
  • Напишіть код, який його вирішує, напишіть все одним методом (наприклад: основний);
  • Ретельно проаналізуйте код і, спираючись на бізнес, спробуйте визначити обов'язки, які видно у всіх операціях, що виконуються (це суб’єктивна частина, яка також залежить від бізнесу / проекту / замовника);
  • Зверніть увагу, що вся функціональність вже реалізована; далі - лише організація коду (відтепер у цьому підході жодна додаткова функція чи механізм не буде реалізована);
  • На основі обов'язків, визначених у попередніх кроках (які визначені на основі бізнесу та ідеї "одна причина змінити"), витягніть для кожного окремий клас чи метод;
  • Зверніть увагу, що цей підхід стосується лише СПР; в ідеалі тут повинні бути додаткові кроки, намагаючись дотримуватися й інших принципів.

Приклад:

Проблема: отримати від користувача два числа, обчислити їх суму та вивести результат користувачеві:

//first step: solve the problem right away
static void Main(string[] args)
{
    Console.WriteLine("Number 1: ");
    int firstNumber = Convert.ToInt32(Console.ReadLine());

    Console.WriteLine("Number 2: ");
    int secondNumber = Convert.ToInt32(Console.ReadLine());

    int result = firstNumber + secondNumber;

    Console.WriteLine("Hi there! The result is: {0}", result);

    Console.ReadLine();
}

Далі спробуйте визначити обов'язки на основі завдань, які необхідно виконати. З цього витягніть відповідні класи:

//Responsible for getting two integers from the user
class Input {
    public int FirstNumber { get; set; }
    public int SecondNumber { get; set; }
    public void Read() {
        Console.WriteLine("Number 1: ");
        FirstNumber = Convert.ToInt32(Console.ReadLine());

        Console.WriteLine("Number 2: ");
        SecondNumber = Convert.ToInt32(Console.ReadLine());
    }
}

//Responsible for calculating the sum of two integers
class SumOperation {
    public int Result { get; set; }
    public void Calculate(int a, int b) {
        Result = a + b;
    }
}

//Responsible for the output of some value to the user
class Output {
    public void Write(int result) {
        Console.WriteLine("Hello! The result is: {0}", result);
    }
}

Потім програма, що реконструюється, стає:

//Program: responsible for main execution.
//Gets two numbers from user and output their sum.
static void Main(string[] args)
{
    var input = new Input();
    input.Read();

    var operation = new SumOperation();
    operation.Calculate(input.FirstNumber, input.SecondNumber);

    var output = new Output();
    output.Write(operation.Result);

    Console.ReadLine();
}

Примітка: цей дуже простий приклад враховує лише принцип SRP. Використання інших принципів (наприклад: "L" - код повинен залежати від абстракцій, а не конкрементів) забезпечить більше користі для коду та зробить його більш доступним для зміни бізнесу.


1
Ваш приклад занадто простий, щоб адекватно проілюструвати SRP. Ніхто цього не зробив би в реальному житті.
Роберт Харві

Так, у реальних проектах я пишу деякий псевдокод, а не записую точний код, як у моєму прикладі. Після псевдокоду я намагаюся розділити обов'язки так, як це робив у прикладі. У всякому разі, саме так я це роблю.
Emerson Cardoso

0

З книги Роберта К. Мартінса « Чиста архітектура: Посібник майстра щодо структури та дизайну програмного забезпечення» , опублікованої 10 вересня 2017 року, Роберт пише на сторінці 62 наступне:

Історично СРП було описано так:

Модуль повинен мати одну, і лише одну, причину для зміни

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

Модуль повинен відповідати одному, і лише одному користувачеві або зацікавленій особі

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

Таким чином, остаточним варіантом СРП є:

Модуль повинен відповідати одному та лише одному актору.

Тож мова не йде про код. ЗРП стосується контролю над потоками потреб та потребами бізнесу, які можуть виходити лише з однієї душі.


Я не впевнений, чому ти робиш відмінність, що "справа не в коді". Звичайно, мова йде про код; це розробка програмного забезпечення.
Роберт Харві

@RobertHarvey Моя думка полягає в тому, що потік вимог відбувається з одного джерела, актора. Користувачі та зацікавлені сторони не вкладаються в код, вони входять у ділові правила, які приходять до нас як вимоги. Тож SRP - це процес контролю цих вимог, який для мене не є кодовим. Це розробка програмного забезпечення (!), Але не код.
Бенні Скогберг
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.