MVC:控制器是否违反了单一责任原则?


16

单一责任原则指出“班级应该有一个改变的理由”。

在MVC模式中,Controller的工作是在视图和模型之间进行中介。它为View提供了一个界面,以报告用户在GUI上进行的操作(例如,允许View调用controller.specificButtonPressed()),并且能够在Model上调用适当的方法,以操纵其数据或调用其操作(例如model.doSomething()) 。

这意味着:

  • 控制器需要了解GUI,以便为“视图”提供合适的界面来报告用户操作。
  • 它还需要了解模型中的逻辑,以便能够在模型上调用适当的方法。

这意味着有两个更改的原因:GUI的更改和商务逻辑的更改。

如果GUI发生更改,例如添加了新按钮,则控制器可能需要添加新方法,以允许视图报告用户对该按钮的按下情况。

而且,如果模型中的业务逻辑发生了变化,则控制器可能必须进行更改才能在模型上调用正确的方法。

因此,控制器有两个可能的变化原因。它会破坏SRP吗?


2
控制器是一条2条路,不是典型的自上而下或自下而上的方法。它不可能抽象出其依赖项之一,因为控制器本身就是抽象。由于图案的性质,此处无法遵守SRP。简而言之:是的,它违反了SRP,但这是不可避免的。
Jeroen Vannevel 2014年

1
问题的重点是什么?如果大家都回答“是,就是”,那该怎么办?如果答案为“否”怎么办?您要解决的真正问题是什么?
布莱恩·奥克利

1
“更改的理由”并不意味着“更改的代码”。如果您在班级的变量名中输入了七个错字,那么该班级现在有7个职责吗?否。如果您具有多个变量或一个以上功能,那么您可能还仍然只有一个职责。
鲍勃

Answers:


14

因此,如果您继续对SRP进行推理,您会发现“单一职责”实际上是一个海绵状术语。我们的人脑以某种方式能够区分不同的责任,多种责任可以抽象为一种“一般”责任。例如,假设在一个普通的4人家庭中,有一位家庭成员负责做早餐。现在,要做到这一点,就必须煮鸡蛋和吐司面包,当然还要设置一些健康的绿茶(是的,最好是绿茶)。这样,您可以将“做早餐”分解成较小的部分,将它们抽象为“做早餐”。请注意,每件作品也是一项责任,例如可以委派给另一个人。

回到MVC:如果在模型和视图之间进行调解不是一个责任而是两个责任,那么结合这两个责任的第二层是什么?如果找不到一个,则可能是您没有正确地对其进行抽象,或者没有一个意味着您一切正常。我觉得控制器,处理视图和模型就是这种情况。


1
只是作为补充说明。如果您有controller.makeBreakfast()和controller.wakeUpFamily(),则该控制器将破坏SRP,但这不是因为它是一个控制器,而是因为它承担了多个责任。
鲍勃

感谢您的回答,不确定我是否关注您。您是否同意控制者承担多项责任?我之所以这样认为,是因为它有两个改变的原因(我认为):模型的改变和视图的改变。你同意吗?
阿维夫·科恩

1
是的,我可以同意控制器承担多项责任。但是,这些是“较低”的职责,这不是问题,因为在最高抽象级别,它仅具有一个职责(结合您提到的较低职责),因此它不违反SRP。
瓦特里

1
找到正确的抽象级别绝对重要。“做早餐”示例是一个很好的例子-要完成一项职责,通常必须完成许多任务。只要控制器仅协调这些任务,它就会遵循SRP。但是,如果它对煮鸡蛋,烤面包或冲泡茶了解得太多,那么它将违反SRP。
艾伦2014年

这个答案对我来说很有意义。谢谢你瓦伦特里。
J86

9

如果一个类具有“两个可能的改变原因”,那么是的,它违反了SRP。

控制器通常应该是轻量级的,并具有响应某些GUI驱动的事件来操纵域/模型的单一责任。我们可以将这些操作中的每一个基本上视为用例或功能。

如果在GUI上添加了新按钮,则仅当该新按钮代表某些新功能时(例如,与屏幕1上存在但屏幕2上尚不存在的同一按钮相反),控制器才应该进行更改。添加到屏幕2)。为了支持此新功能/功能,模型中也将需要进行相应的新更改。控制器仍然只是负责响应某些GUI驱动的事件来操纵域/模型。

如果模型中的业务逻辑由于已修复的错误而发生更改,并且需要控制器进行更改,则这是一种特殊情况(或者该模型违反了开闭原理)。如果模型中的业务逻辑发生更改以支持某些新功能/特性,那么这不一定会影响控制器-仅在控制器需要公开该功能时(几乎总是如此,否则为什么要将其添加到控制器中)。域模型(如果不使用)。因此,在这种情况下,还必须修改控制器,以支持响应某些GUI驱动事件以这种新方式操纵域模型。

如果控制器必须更改(例如,由于将持久层从平面文件更改为数据库),则控制器肯定违反了SRP。如果控制器始终在同一抽象层工作,则可以帮助实现SRP。


4

控制器不违反SRP。如您所述,它的责任是在模型和视图之间进行中介。

话虽如此,您的示例的问题在于您将控制器方法与视图中的逻辑相关联,即controller.specificButtonPressed。以这种方式命名方法会将控制器与GUI绑定在一起,则您无法正确抽象事物。控制器应该关于执行特定动作,即controller.saveDatacontroller.retrieveEntry。在GUI中添加新按钮并不一定意味着向控制器添加新方法。

按下视图中的按钮意味着要执行某项操作,但是无论以其他方式还是通过视图都不容易触发。

摘自维基百科有关SRP的文章

马丁将责任定义为变更的理由,并得出结论,一个类或模块应该只有一个变更理由。例如,考虑一个编译并打印报告的模块。可以出于两个原因而更改这种模块。首先,报告的内容可以更改。其次,报告的格式可以更改。这两件事因不同的原因而改变。一种实质性和一种化妆品。单一责任原则说,问题的这两个方面实际上是两个单独的责任,因此应该放在单独的类或模块中。耦合由于不同原因在不同时间发生变化的两件事,将是一个糟糕的设计。

控制器并不关心视图中的内容,只是当调用其方法之一时,它会向视图提供指定的数据。它只需要知道模型的功能,只要知道它需要调用它们将拥有的方法即可。除此之外,它什么都不知道。

知道对象具有可调用的方法与知道其功能不同。


1
我之所以认为控制器应该包含类似的方法,specificButtonsPressed()是因为我读到视图不应该知道其按钮和其他GUI元素的功能。有人告诉我,当按下按钮时,视图应该简单地向控制器报告,然后控制器应该确定“含义”(然后在模型上调用适当的方法)。进行视图调用controller.saveData()意味着视图必须知道该按钮被按下的含义。
阿维夫·科恩

1
如果我specificButtonPressed()放弃将按钮按下和它的含义完全分开的想法(这导致控制器具有类似的方法),则实际上控制器不会与GUI绑定太多。我应该放弃这些specificButtonPressed()方法吗?我认为拥有这些方法的优势对您有意义吗?还是buttonPressed()控制器中的方法不值得麻烦?
阿维夫·科恩

1
换句话说(很长的评论很抱歉):我认为specificButtonPressed()在控制器中具有方法的优点在于,它可以将View与按钮完全分开的含义分开。但是,缺点是在某种意义上它将控制器与GUI绑定在一起。哪种方法更好?
阿维夫·科恩

@prog IMO控制器应该对视线不可见。按钮将具有某种功能,但不需要知道其详细信息。它只需要知道它正在向控制器方法发送一些数据。在这方面,名称无关紧要。可以调用它,也可以foo很容易地调用它fireZeMissiles。它只会知道它应该向特定功能报告。它不知道函数会做什么,只会调用它。控制器并不关心如何调用其方法,而只是在它们调用时会以某种方式做出响应。
Schleis 2014年

1

控制器的单一职责是在视图和模型之间进行中介的合同。视图应该只负责显示,模型应该只负责业务逻辑。 桥接这两个责任是控制者的责任。

一切都很好,但是要冒险离开学术界。MVC中的控制器通常由许多较小的操作方法组成。这些动作通常对应于某件事情可以做的事情。如果我要销售产品,则可能会有一个ProductController。该控制器将执行诸如GetReviews,ShowSpecs,AddToCart等操作。

视图具有显示UI的SRP,并且该UI的一部分包括一个显示AddToCart的按钮。

控制器具有了解过程中涉及的所有视图和模型的SRP。

控制器AddToCart Action具有特定的SRP,可以在将商品添加到购物车时了解需要参与的每个人。

产品模型具有对产品逻辑进行建模的SRP,而ShoppingCart模型具有对如何保存商品以供以后结帐的建模的SRP。用户模型具有对要向购物车中添加商品的用户进行建模的SRP。

您可以并且应该重用模型来完成您的业务,并且这些模型需要在代码中的某些时候进行耦合。控制器控制耦合发生的每种独特方式。


0

控制器实际上只有一个责任:根据用户的输入更改应用程序状态。

一个控制器可以发送命令到模型更新模型的状态(例如,编辑文档)。它还可以将命令发送到其关联的视图,以更改模型的视图表示(例如,通过滚动文档)。

source: wikipedia

相反,如果您使用的是Rails风格的“控制器” (用于处理活动记录实例和哑模板),则当然是在打破SRP。

再说一次,Rails风格的应用程序并不是真正的MVC。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.