上帝控制器 - 如何防止它们?
Posted
技术标签:
【中文标题】上帝控制器 - 如何防止它们?【英文标题】:God Controllers - How to prevent them? 【发布时间】:2010-11-04 16:26:06 【问题描述】:在我从事的一些 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 级别)。
我想错了吗?例如,我是否应该有一个通用的收藏夹对象,然后让控制器决定将它扔到哪个视图?
*抱歉拼出首字母缩略词——我这样做是为了防止其他人遇到这个问题并且对这些东西是什么一无所知
编辑: 我执行的所有逻辑几乎都在服务层中处理。例如,控制器会将“新”FoodItem 发送到服务。如果它已经存在,或者它有错误,服务会将它冒泡回控制器。
【问题讨论】:
【参考方案1】:我会根据责任分解你的第一个列表:
HomeController
索引FoodItemController
查看FoodItem 添加食物项 编辑食物项 删除食品项 搜索FoodItem营养控制器
查看营养总结FavoritesController
添加到收藏夹 从收藏夹中删除 查看收藏夹 搜索收藏夹Django 的 MVC 方法是将职责分离到“应用程序”中,每个应用程序都有自己的模型、控制器,甚至在必要时使用模板。您很可能会拥有一个 Food 应用、一个 Nutrition 应用、一个 Search 应用和一个 Favorites 应用。
编辑:OP 提到搜索更具体到每个控制器,所以我做了这些操作。但是,搜索也可能只是一个通用的全局事物,因此在这些情况下,SearchController 就可以了。
【讨论】:
那么我会为练习复制相同的东西还是将它们添加到那些控制器中? IE。 SearchController 会处理 SearchExerciseItem 方法,还是会是另一个控制器,例如 SearchExerciseController? 如果您是这样设置的,那么搜索是控制器上的操作,此时不是控制器本身。如果搜索是通用的,那么它可能是它自己的控制器。我修改了我的答案以反映这一点。 MVC 以一种非常自然的方式与 REST 一起工作:所有控制器“控制”一种资源并知道如何对该资源执行各种操作(即响应传递给该资源的各种消息) ),REST 资源种类映射到域模型实体类型大多是一对一的(这就是拥有域模型的意义)。【参考方案2】:照苏维特说的做。你想让控制器保持简单。听起来您最终在控制器中使用了太多的协调逻辑。请记住,他们负责连接视图和模型。这种协调逻辑可能应该被拆分为服务。
我有这种感觉是因为您提到您的控制器可能会产生巨大的依赖关系。好吧,如果 FavoritesController 需要了解营养和锻炼收藏(显示在同一视图中),请不要让您的控制器依赖于 2 个存储库,如类。相反,封装该协调行为。也许创建一个知道如何返回营养和运动收藏夹的收藏夹服务。该服务可能会委托给 NutritionFavoritesService 和 ExerciseFavoritesService。这样一来,您的控制器只会以 1 个依赖项结束,您要保持 DRY,执行 SRP,并将业务逻辑集中在控制器以外的某个地方。
【讨论】:
我在服务中有大部分协调逻辑,但似乎我的控制器和服务层都有非常具体的方法。例如,控制器将对服务层进行“GetFavoriteFoodItemsForUser”调用,在该层我处理所有内容并返回一个列表,然后控制器将其转储到视图中。 啊。我会尝试提出一些一般规则并更新我的答案。对于您提供的示例,我可能有一个 UserController,其方法 FavoirteFoodTiems 只接受 HttpMetthod GET。【参考方案3】:我对这个框架不太熟悉,但我可以提供一些一般性建议。控制器可能应该只知道如何完成单个动作,或者调用其他单个动作控制器来完成一系列相关动作。任何必须在动作之间传递的信息都应该以某种方式通过模型层传递,因为该信息很可能与底层模型相关。
【讨论】:
这不是它在许多 MVC 框架(例如 Rails、ASP.NET MVC)中的工作方式。单个控制器知道如何对一种实体执行许多操作,但不应该知道如何对其他种类的实体执行任何操作。【参考方案4】:我也经历过这类维护难题,并发现坚持使用类似“Rails”的方法非常有助于让我的控制器保持专注和不臃肿。
如果我发现自己添加了具有不寻常名称的操作,例如。以博客示例 AddPostToBlog 为例,这将是一个使用 Create 操作创建新 Post 控制器的标志。
换句话说,如果操作不是 Index、New、Create、Show、Edit、Update 和 Destroy 操作之一,那么我添加一个特定于我需要的操作的新控制器。
以你为例。
SearchController
SearchExercise
SearchNutrition
//... etc
我会将其重构为...
SearchExerciseController
Index
SearchNutritionController
Index
这可能意味着拥有更多控制器,但在我看来,这比以往扩展的“上帝”控制器更容易管理。这也意味着控制器更加自我记录。
例如。 SearchExercise 操作是返回视图以搜索练习还是实际执行搜索?您可能可以通过查看参数和正文来确定这一点,但它不像新建和创建或编辑和更新操作对那样简单。
SearchController
SearchExercise
【讨论】:
以上是关于上帝控制器 - 如何防止它们?的主要内容,如果未能解决你的问题,请参考以下文章