MVC: Чи регулятор порушує принцип єдиної відповідальності?


16

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

У шаблоні MVC завданням контролера є посередництво між видом і моделлю. Він пропонує інтерфейс для представлення даних, щоб повідомляти про дії, здійснені користувачем на графічному інтерфейсі (наприклад, дозволити View зателефонувати controller.specificButtonPressed()), а також може викликати відповідні методи в моделі, щоб маніпулювати своїми даними або викликати свої операції (наприклад model.doSomething()) .

Це означає що:

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

Це означає, що є дві причини зміни : зміна GUI та зміна логіки buisness.

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

І якщо бізнес-логіка в Моделі зміниться, Контролеру, можливо, доведеться змінитись, щоб викликати правильні методи в Моделі.

Тому у Контролера є дві можливі причини зміни . Це порушує SRP?


2
Контролер - це двостороння вулиця, це не ваш типовий підхід зверху вниз або знизу вгору. Немає можливості абстрагувати одну зі своїх залежностей, оскільки контролером є сама абстракція. Зважаючи на характер шаблону, тут неможливо дотримуватися СРП. Коротше кажучи: так, це порушує SRP, але це неминуче.
Джероен Ванневель

1
Який сенс питання? Якщо ми всі відповідаємо "так, це робить", що тоді? Що робити, якщо відповідь "ні"? Яку справжню проблему ви намагаєтеся вирішити за допомогою цього питання?
Брайан Оуклі

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

Відповіді:


14

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

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


1
Так само як додана примітка. Якщо у вас є controller.makeBreakfast () та controller.wakeUpFamily (), тоді цей контролер порушує SRP, але не тому, що це контролер, а лише тому, що він несе більше відповідальності.
Боб

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

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

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

Ця відповідь для мене має сенс. Дякую, Валентері.
J86

9

Якщо у класу є "дві можливі причини зміни", то так, це порушує SRP.

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

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

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

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


4

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

За словами, проблема з вашим прикладом полягає в тому, що ви прив'язуєте контролерні методи до логіки в погляді, тобто controller.specificButtonPressed. Назвавши методи таким чином, зв’язуючи контролер з вашим графічним інтерфейсом, ви не змогли правильно абстрагувати речі. Контролер повинен говорити про виконання конкретних дій, тобто controller.saveDataабо controller.retrieveEntry. Додавання нової кнопки в графічний інтерфейс не обов'язково означає додавання нового методу до контролера.

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

З статті Вікіпедії про СРП

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

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

Знання того, що об’єкт має доступний для виклику метод, не те саме, що знати його функціональність.


1
Причиною, на яку я вважав, що контролер повинен включати такі способи, - specificButtonsPressed()це те, що я читав, що представлення не повинна нічого знати про функціональність його кнопок та інших елементів графічного інтерфейсу. Мене вчили, що при натисканні кнопки погляд повинен просто звітувати перед контролером, а контролер повинен вирішити "що це означає" (а потім застосувати відповідні методи на моделі). Здійснення виклику перегляду controller.saveData()означає, що погляд повинен знати про те, що означає це натискання кнопки, крім того, що вона була натиснута.
Авів Кон

1
Якщо я відкидаю ідею повного поділу між натисканням кнопки та її сенсом (що призводить до того, що контролер має такі способи specificButtonPressed()), дійсно контролер не настільки прив’язаний до GUI. Чи слід я відкидати specificButtonPressed()методи? Чи є перевага, на який я думаю, що ці методи мають для вас сенс? Або використання buttonPressed()методів у контролері не варте клопоту?
Авів Кон

1
Іншими словами (вибачте за довгі коментарі): Я думаю, що перевагою наявності specificButtonPressed()методів у контролері є те, що він відокремлює Погляд від значення його кнопки повністю натискає . Однак недоліком є ​​те, що він певним чином пов’язує контролер з графічним інтерфейсом. Який підхід кращий?
Авів Кон

@prog IMO Контролер повинен бути сліпим для перегляду. Кнопка матиме певну функціональність, але не потрібно знати її деталі. Просто потрібно знати, що він надсилає деякі дані в метод контролера. У цьому плані ім'я не має значення. Це можна назвати foo, або так само легко fireZeMissiles. Знатиме лише те, що він повинен звітувати перед певною функцією. Він не знає, що функція виконує тільки те, що буде викликати її. Контролер не переймається тим, як викликаються його методи, лише якщо він відповість певними способами, коли вони є.
Шлейс

1

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

Це все добре і добре, але трохи відходити від академій; контролер в MVC, як правило, складається з безлічі менших методів дії. Ці дії, як правило, відповідають речам, які можна зробити. Якщо я продаю продукцію, напевно, у мене буде ProductController. Цей контролер матиме такі дії, як GetReviews, ShowSpecs, AddToCart ect ...

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

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

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

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

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


0

Насправді контролери несуть лише одну відповідальність: зміна стану додатків на основі введення користувача.

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

source: wikipedia

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

Знову ж таки, програми в стилі Rails - це насправді не MVC.

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