Контроллеры богов — как их предотвратить?

В нескольких проектах MVC, над которыми я работал, стало очевидно, что есть несколько проблематичных контроллеров, которые органично превратились в классы богов — полубогов, каждый в своей области, если хотите.

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

В одном проекте у нас есть NutritionController. Со временем он расширился и теперь включает следующие действия (многие с соответствующими методами GET, POST и DELETE):

Index (home controller)
ViewFoodItem
AddFoodItem
EditFoodItem
DeleteFoodItem
ViewNutritionSummary
SearchFoodItem
AddToFavorites
RemoveFromFavorites
ViewFavorites

Затем у нас есть ExerciseController, который будет включать в себя множество подобных действий, таких как поиск и избранное. Должны ли они быть реорганизованы в свой собственный контроллер, чтобы это было примерно так?

SearchController {
    SearchExercise
    SearchNutrition
    //... etc
}

FavoritesController {
    ViewNutritionFavorites
    AddToNutritionFavorites
    AddToExerciseFavorites
    EditNutritionFavorites
    EditExerciseFavorites
    //... etc
}

Мне просто кажется, что если разбить их на отдельные контроллеры, то на каком-то уровне вырастет невероятно большая зависимость для обработки информации, которая вам понадобится. ИЛИ у вас будет полностью универсальное приложение для обработки, с которым будет очень сложно справиться, поскольку вам придется прыгать через очень много обручей, чтобы получить желаемый эффект (либо на уровне M, V, либо C).

Я думаю об этом неправильно? Например, должен ли я иметь общий объект «Избранное», а затем позволить контроллеру решить, в какое представление его передать?

* Извините за расшифровку аббревиатур - я делаю это на случай, если кто-то еще столкнется с этим вопросом и не знает, что это за вещи.

EDIT: вся логика, которую я выполняю, в значительной степени обрабатывается на уровнях сервиса. Например, контроллер отправит службе «новый» объект FoodItem. Если он уже существует или с ним произошла ошибка, служба отправит его обратно на контроллер.


person MunkiPhD    schedule 21.06.2009    source источник


Ответы (4)


Я бы разбил ваш первый список в зависимости от ответственности:

Контроллер дома

  • Индекс

Контроллер продуктов питания

  • ViewFoodItem
  • AddFoodItem
  • EditFoodItem
  • DeleteFoodItem
  • ПоискЕдаЭлемент

Контроллер питания

  • ViewNutritionСводка

Контроллер избранного

  • Добавить в избранное
  • Удалить из Избранного
  • ПосмотретьИзбранное
  • ПоискИзбранное

Подход Django к MVC заключается в разделении обязанностей на "приложения", каждое со своими моделями, контроллерами и даже шаблоны при необходимости. Скорее всего, у вас будет приложение «Еда», приложение «Питание», приложение «Поиск» и приложение «Избранное».

Изменить: ОП упомянул, что поиск более специфичен для каждого контроллера, поэтому я сделал эти действия. Однако поиск также может быть просто общей глобальной вещью, поэтому в таких случаях SearchController будет в порядке.

person Soviut    schedule 21.06.2009
comment
Итак, мне следует воспроизвести те же самые вещи для упражнений или добавить их к этим контроллерам? т.е. будет ли SearchController обрабатывать методы SearchExerciseItem или это будет другой контроллер, такой как SearchExerciseController? - person MunkiPhD; 21.06.2009
comment
Если вы так настроили, то в данный момент поиск является действием на контроллере, а не на самом контроллере. Если бы поиск был чем-то общим, то это мог бы быть его собственный контроллер. Я изменил свой ответ, чтобы отразить это. - person Soviut; 22.06.2009
comment
MVC работает с REST очень естественным образом: все ваши контроллеры управляют одним типом ресурса и знают, как выполнять различные действия с этим ресурсом (т. е. отвечать на различные сообщения, переданные этому ресурсу), при этом типы ресурсов REST сопоставляются с доменом. типы сущностей модели в основном один к одному (в этом суть модели предметной области). - person yfeldblum; 22.06.2009

Делай, как говорит Совет. Вы хотите, чтобы контроллеры были простыми. Похоже, у вас слишком много логики координации в ваших контроллерах. Помните, что они отвечают за подключение представления и модели. Эта логика координации, вероятно, должна быть разделена на службы.

У меня такое чувство, потому что вы упомянули о возможности роста огромных зависимостей вашего контроллера. Что ж, если FavoritesController должен знать о питании и избранных упражнениях (для отображения в одном и том же представлении), не делайте ваш контроллер зависимым от двух репозиториев, таких как классы. Вместо этого инкапсулируйте это координационное поведение. Может быть, создать службу FavoritesService, которая знает, как возвращать избранное как по питанию, так и по упражнениям. Эта служба может делегировать функции NutritionFavoritesService и ExerciseFavoritesService. Таким образом, ваш контроллер заканчивается только с 1 зависимостью, вы держите вещи СУХИМИ, применяете SRP и концентрируете свою бизнес-логику в каком-то другом месте, кроме контроллера.

person Dane O'Connor    schedule 21.06.2009
comment
У меня есть большая часть логики координации в сервисах, но кажется, что у меня есть очень специфические методы как в моих контроллерах, так и в сервисных слоях. Например, у контроллера будет вызов GetFavoriteFoodItemsForUser на уровне службы, где я обрабатываю все и возвращаю список, который затем контроллер выводит в представление. - person MunkiPhD; 21.06.2009
comment
Ах. Я постараюсь придумать некоторые общие правила и обновить свой ответ. В приведенном вами примере у меня, вероятно, будет UserController с методом FavoirteFoodTiems, который принимает только HttpMetthod GET. - person Dane O'Connor; 21.06.2009

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

person SingleNegationElimination    schedule 21.06.2009
comment
Это не так, как это работает во многих средах MVC (например, Rails, ASP.NET MVC). Отдельные контроллеры знают, как выполнять множество действий с объектами одного типа, но не должны знать, как выполнять какие-либо действия с объектами других типов. - person yfeldblum; 22.06.2009

Я также сталкивался с подобными головными болями, связанными с обслуживанием, и считаю, что придерживаться метода, подобного «Rails», очень полезно для того, чтобы мои контроллеры были сфокусированы и не раздуты.

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

Другими словами, если действие не является ни одним из действий Index, New, Create, Show, Edit, Update и Destroy, я добавляю новый контроллер, специфичный для требуемого действия.

Для вашего примера.

SearchController {
    SearchExercise
    SearchNutrition
    //... etc
}

Я бы реорганизовал это, чтобы...

   SearchExerciseController {
           Index   
   }

   SearchNutritionController {
           Index   
   }

Это может означать наличие большего количества контроллеров, но, на мой взгляд, ими легче управлять, чем когда-либо расширять «божественные» контроллеры. Это также означает, что контроллеры лучше самодокументируются.

Например. Действие SearchExercise возвращает представление для поиска упражнения или оно действительно выполняет поиск? Вероятно, вы могли бы убедиться в этом, посмотрев на параметры и тело, но это не так просто, как, например, пара действий «Создать» и «Создать» или «Редактировать и обновить».

SearchController {
    SearchExercise       
}
person Matt    schedule 02.08.2010