MVC: Контролер нарушает принцип единой ответственности?

16

Принцип Единой Ответственности гласит, что «класс должен иметь одну причину изменения».

В паттерне MVC работа Контроллера заключается в том, чтобы быть посредником между представлением и моделью. Он предлагает интерфейс для представления отчетов о действиях, выполненных пользователем в графическом интерфейсе (например, позволяя представлению вызывать controller.specificButtonPressed()), и способен вызывать соответствующие методы в модели для манипулирования данными или вызова их операций (например, model.doSomething()) ,

Это означает, что:

  • Контроллер должен знать о графическом интерфейсе, чтобы предложить View подходящий интерфейс для отчета о действиях пользователя.
  • Также необходимо знать логику в модели, чтобы иметь возможность вызывать соответствующие методы в модели.

Это означает, что у этого есть две причины для изменения : изменение в GUI и изменение в логике бизнеса.

Если изменяется графический интерфейс пользователя, например, добавляется новая кнопка, контроллеру может потребоваться добавить новый метод, чтобы позволить представлению сообщать о нажатии на эту кнопку пользователем.

И если бизнес-логика в модели изменится, возможно, придется изменить контроллер, чтобы вызвать правильные методы в модели.

Поэтому у контроллера есть две возможные причины для изменения . Это нарушает SRP?

Авив Кон
источник
2
Контроллер - это улица с двусторонним движением, это не ваш типичный нисходящий или восходящий подход. У него нет возможности абстрагировать одну из своих зависимостей, потому что контроллер сам является абстракцией. Из-за характера шаблона здесь невозможно придерживаться SRP. Короче говоря: да, это нарушает SRP, но это неизбежно.
Йерун Ванневел
1
В чем суть вопроса? Если мы все ответим «да, это так», что тогда? Что если ответ «нет»? Какую реальную проблему вы пытаетесь решить с помощью этого вопроса?
Брайан Оукли
1
«причина для изменения» не означает «код, который изменяется». Если вы сделаете семь опечаток в именах переменных для класса, будет ли у этого класса 7 обязанностей? Нет. Если у вас есть более одной переменной или более одной функции, вы также можете иметь только одну ответственность.
Боб

Ответы:

14

Если вы продолжите, следовательно, рассуждать о SRP, вы заметите, что «одиночная ответственность» - это на самом деле нечеткий термин. Наш человеческий мозг каким-то образом способен различать разные обязанности, а множественные обязанности можно абстрагировать в одну «общую» ответственность. Например, представьте, что в общей семье из 4 человек есть один член семьи, ответственный за приготовление завтрака. Теперь, чтобы сделать это, нужно сварить яйца и тостовый хлеб и, конечно, приготовить здоровую чашку зеленого чая (да, зеленый чай лучше). Таким образом, вы можете разбить «приготовление завтрака» на более мелкие кусочки, которые вместе абстрагируются от «приготовления завтрака». Обратите внимание, что каждая часть также является обязанностью, которая, например, может быть передана другому лицу.

Возвращаясь к MVC: если посредничество между моделью и представлением - это не одна ответственность, а две, то каков будет следующий уровень абстракции, объединяющий эти две? Если вы не можете найти ни одного, вы либо не правильно его абстрагировали, либо его нет, что означает, что вы все правильно поняли. И я чувствую, что это имеет место с контроллером, обрабатывающим представление и модель.

valenterry
источник
1
Так же, как добавленная заметка. Если у вас есть controller.makeBreakfast () и controller.wakeUpFamily (), то этот контроллер будет нарушать SRP, но не потому, что он является контроллером, просто потому, что он несет более одной ответственности.
Боб
Спасибо за ответ, не уверен, что следую за вами. Согласны ли вы с тем, что контроллер имеет более одной ответственности? Причина, по которой я так думаю, заключается в том, что у нее есть две причины для изменения (я думаю): изменение модели и изменение представления. ты согласен с этим?
Авив Кон
1
Да, я могу согласиться с тем, что контроллер имеет более одной ответственности. Однако это «более низкие» обязанности, и это не проблема, потому что на самом высоком уровне абстракции он несет только одну ответственность (объединяя упомянутые вами более низкие) и поэтому не нарушает SRP.
valenterry
1
Найти правильный уровень абстракции, безусловно, важно. Хороший пример - «приготовить завтрак» - для выполнения одной обязанности часто необходимо выполнить ряд задач. Пока контроллер выполняет только эти задачи, он следует SRP. Но если он знает слишком много о варке яиц, приготовлении тостов или заваривании чая, то это нарушит ПСП.
Аллан
Этот ответ имеет смысл для меня. Спасибо, Валентерри.
J86
9

Если у класса есть «две возможные причины для изменения», то да, это нарушает SRP.

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

Если в GUI добавлена ​​новая кнопка, контроллер должен измениться только в том случае, если эта новая кнопка представляет какую-то новую функцию (то есть, в отличие от той же кнопки, которая существовала на экране 1, но еще не существовала на экране 2, и тогда добавлено на экран 2). Для поддержки этой новой функциональности / функции также потребуется соответствующее новое изменение в модели. Контроллер по-прежнему несет ответственность за манипулирование доменом / моделью в ответ на какое-либо событие с GUI-интерфейсом.

Если бизнес-логика в модели изменяется из-за исправляемой ошибки и требует изменения контроллера, то это особый случай (или, возможно, модель нарушает принцип «открытый-закрытый»). Если бизнес-логика в модели изменяется для поддержки какой-либо новой функциональности / функции, то это не обязательно влияет на контроллер - только если контроллеру необходимо предоставить эту функцию (что почти всегда имеет место, в противном случае зачем ее добавлять в модель предметной области, если она не будет использоваться). Таким образом, в этом случае контроллер также должен быть модифицирован, чтобы поддерживать манипулирование моделью домена новым способом в ответ на какое-либо событие, управляемое GUI.

Если контроллер должен измениться, потому что, скажем, уровень постоянства изменяется с простого файла на базу данных, то контроллер, безусловно, нарушает SRP. Если контроллер всегда работает на одном и том же уровне абстракции, это может помочь в достижении SRP.

Иордания
источник
4

Контроллер не нарушает SRP. Как вы заявляете, его обязанность заключается в посредничестве между моделями и представлением.

Это , как говорится, вопрос с вашим примером является то , что вы зафиксируете контроллер методов логики в представлении, то есть controller.specificButtonPressed. Называя методы таким образом, связывает контроллер с вашим GUI, вы не смогли должным образом абстрагировать вещи. Контроллер должен выполнять определенные действия, т . Е. controller.saveDataИли controller.retrieveEntry. Добавление новой кнопки в GUI не обязательно означает добавление нового метода в контроллер.

Нажатие кнопки в представлении означает, что нужно что-то делать, но что бы это ни было, оно легко может быть вызвано любым другим способом или даже не через представление.

Из статьи Википедии о SRP

Мартин определяет ответственность как причину изменения и приходит к выводу, что у класса или модуля должна быть одна и только одна причина для изменения. В качестве примера рассмотрим модуль, который компилирует и печатает отчет. Такой модуль может быть изменен по двум причинам. Во-первых, содержание отчета может измениться. Во-вторых, формат отчета может измениться. Эти две вещи меняются по разным причинам; одно основное и одно косметическое. Принцип единой ответственности говорит о том, что эти два аспекта проблемы на самом деле являются двумя отдельными обязанностями и поэтому должны быть в отдельных классах или модулях. Было бы плохим проектом соединить две вещи, которые меняются по разным причинам в разное время.

Контроллер не занимается тем, что находится в представлении, только тем, что при вызове одного из его методов он предоставляет определенные данные представлению. Он должен знать только о функциональности модели, поскольку ему необходимо вызывать методы, которые у них будут. Он не знает ничего больше, чем это.

Знание того, что объект имеет метод, доступный для вызова, не то же самое, что знание его функциональности.

Schleis
источник
1
Причина, по которой я думал, что контроллер должен включать подобные методы, specificButtonsPressed()состоит в том, что я прочитал, что представление ничего не должно знать о функциональности его кнопок и других элементов графического интерфейса. Меня учили, что при нажатии кнопки представление должно просто сообщать контроллеру, а контроллер должен решить, «что это значит» (и затем вызвать соответствующие методы в модели). Выполнение вызова представления controller.saveData()означает, что представление должно знать о том, что означает нажатие этой кнопки, помимо того, что она была нажата.
Авив Кон
1
Если бы я отказался от идеи полного разделения между нажатием кнопки и ее значением (что приводит к тому, что у контроллера есть такие методы specificButtonPressed()), действительно, контроллер не будет так сильно привязан к GUI. Должен ли я отказаться от specificButtonPressed()методов? Имеет ли смысл использовать эти методы для вас? Или buttonPressed()методы в контроллере не стоят проблем?
Авив Кон
1
Другими слова (извините за длинные комментарии): Я думаю , что преимущество наличия specificButtonPressed()методов в контроллере, является то , что он отделяет представление от смысла это нажатие кнопок полностью . Однако недостатком является то, что он в некотором смысле связывает контроллер с графическим интерфейсом. Какой подход лучше?
Авив Кон
@prog IMO Контроллер должен быть закрыт для просмотра. Кнопка будет иметь какую-то функциональность, но ей не нужно знать подробности. Нужно просто знать, что он отправляет некоторые данные в метод контроллера. В этом отношении имя не имеет значения. Это можно назвать fooили так же легко fireZeMissiles. Он будет только знать, что он должен отчитываться перед определенной функцией. Он не знает, что делает функция, только то, что будет вызывать ее. Контроллер не заботится о том, как его методы вызываются только потому, что он будет реагировать определенным образом, когда они есть.
Schleis
1

Единственной обязанностью контролера является контракт, который является посредником между представлением и моделью. Представление должно отвечать только за отображение, модель должна отвечать только за бизнес-логику. Ответственность за преодоление этих двух обязанностей лежит на контролерах.

Это все хорошо, но немного отойти от научных кругов; Контроллер в MVC обычно состоит из множества меньших методов действия. Эти действия обычно соответствуют вещам, которые может сделать вещь. Если я продаю продукты, у меня, вероятно, будет ProductController. Этот контроллер будет иметь такие действия, как GetReviews, ShowSpecs, AddToCart и т. Д.

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

Контроллер имеет SRP, позволяющий узнать все виды и модели, участвующие в процессе.

В контроллере AddToCart Action есть определенный SRP, позволяющий знать всех, кто должен быть вовлечен при добавлении товара в корзину.

Модель продукта имеет SRP моделирования логики продукта, а модель ShoppingCart имеет SRP моделирования, как элементы сохраняются для последующей проверки. Модель пользователя имеет SRP для моделирования пользователя, который добавляет вещи в свою корзину.

Вы можете и должны повторно использовать модели, чтобы сделать свой бизнес, и эти модели должны быть связаны в какой-то момент в вашем коде. Контроллер контролирует каждый уникальный способ, которым происходит соединение.

WhiteleyJ
источник
0

Контроллеры на самом деле несут только одну ответственность: изменяют состояние приложений на основе пользовательского ввода.

Контроллер может посылать команды на модель для обновления состояния модели (например, редактирование документа). Он также может отправлять команды в связанный с ним вид для изменения представления модели (например, путем прокрутки документа).

source: wikipedia

Если вместо этого у вас есть «контроллеры» в стиле Rails (которые манипулируют активными экземплярами записей и тупыми шаблонами) , то, конечно, нарушается SRP.

С другой стороны, приложения в стиле Rails изначально не являются MVC.

Mefisto
источник