确保每个班级只有一个责任,为什么?


37

根据Microsoft文档,有关Wikipedia SOLID原则的文章或大多数IT架构师,我们必须确保每个班级仅负一个责任。我想知道为什么,因为如果每个人似乎都同意这条规则,那么似乎没人会同意这条规则的原因。

一些人引用了更好的维护,有人说它提供了简单的测试,或者使类更加健壮或安全。什么是正确的,实际上是什么意思?为什么它可以使维护更好,测试更容易或代码更健壮?



1
我有一个可能也与您相关的问题:课堂的真正责任是什么?
皮埃尔·阿洛德

3
单一责任的所有原因都不正确吗?它确实使维护更加容易。它确实使测试更加容易。它确实使类更加健壮(通常)。
马丁·约克

2
出于同样的原因,您也有课程。
DavorŽdralo2014年

2
我一直对此声明有疑问。定义什么是“单一责任”是非常困难的。单一责任的范围可以从“验证1 + 1 = 2”到“维护准确记录公司银行帐户内进出的所有资金”的范围。
Dave Nay 2014年

Answers:


57

模块化。任何体面的语言都会为您提供将代码段粘合在一起的方法,但是没有程序员在源头上不做手术的一般方法,就无法解开大段代码。通过将许多任务卡在一个代码结构中,您会失去自己和其他人机会,以其他方式组合其各个部分,并引入不必要的依赖关系,这些不必要的依赖关系可能导致对一个部分的更改影响其他部分。

SRP适用于函数,也适用于类,但是主流的OOP语言在将函数粘合在一起方面相对较差。


12
+1表示将函数式编程作为组成抽象的更安全方法。
logc 2014年

>但是主流的OOP语言在将功能粘合在一起方面相对较差。<也许是因为OOP语言与功能无关,而与消息有关。
杰夫·哈伯德

@JeffHubbard我认为您的意思是因为它们采用C / C ++。没有什么对象可以使它们与函数互斥。地狱,对象/接口只是功能的记录/结构。从理论上和实践上都没有必要假装功能不重要。无论如何都无法避免它们-您最终不得不四处乱扔“ Runnables”,“ Factories”,“ Providers”和“ Actions”,或者使用所谓的“策略和模板方法”“模式”,最后得到一堆不必要的样板来完成相同的工作。
2014年

@Doval不,我确实是说OOP与消息有关。这并不是说给定的语言在将功能粘合在一起时比功能编程语言好还是坏-只是这不是该语言的主要关注点
杰夫·哈伯德

基本上,如果您使用的语言是使OOP变得更容易/更好/更快/更强,那么您关注的重点不是功能是否很好地粘合在一起。
Jeff Hubbard

30

更好的维护,轻松的测试,更快的错误修复是(非常愉快)应用SRP的结果。主要原因(如Robert C. Matin所说)是:

一堂课应该只有一个改变的理由。

换句话说,SRP提出了变更地点

SRP还促进DRY代码。只要我们的班级只负责一项工作,我们就可以选择在需要的任何地方使用它们。如果我们的班级有两个职责,但我们只需要其中一个职责,而第二个职责就是干预,那么我们有两个选择:

  1. 将班级复制粘贴到另一个班级,甚至可能创建另一个多责任突变体(稍微增加技术债务)。
  2. 首先对类进行划分并按原样进行设置,由于原始类的广泛使用可能会非常昂贵。

1
+1用于将SRP链接到DRY。
Mahdi 2014年

21

创建代码来解决特定问题很容易。创建更正该问题的代码,同时允许以后进行安全更改的代码更加复杂。SOLID提供了一组使代码更好的实践。

关于哪一个是正确的:全部三个。这些都是使用单一职责的好处,也是您应该使用它的原因。

关于它们的含义:

  • 更好的维护意味着更容易更改,而且不会经常更改。因为代码更少,并且该代码专注于特定的内容,所以如果您需要更改与该类无关的内容,则无需更改该类。此外,当您需要更改类时,只要不需要更改公共接口,就只需要担心该类,而无需担心。
  • 简单的测试意味着更少的测试,更少的设置。类别上没有那么多活动部件,因此类别上可能发生的故障数量更少,因此需要测试的案例更少。将会设置更少的私人领域/成员。
  • 由于上述两个原因,您获得了一个变化少,失败少的类,因此更加健壮。

请尝试遵循该原理一段时间来创建代码,然后稍后重新访问该代码以进行一些更改。您将看到它提供的巨大优势。

您对每个班级都做同样的事情,最后得到更多班级,所有班级都符合SRP。从连接的角度来看,它使结构更加复杂,但是每个类的简单性证明了这一点。


我认为这里提出的观点是没有道理的。特别是,如何避免零和博弈,因为简化类会导致其他类变得更加复杂,而对整个代码库没有任何净影响?我相信@Doval的答案确实解决了这个问题。

2
您对所有班级都做同样的事情。您将获得更多符合SRP的课程。从连接的角度来看,它使结构更加复杂。但是每个类的简单性证明了这一点。不过,我喜欢@Doval的答案。
宫本晃2014年

我认为您应该将其添加到答案中。

4

我认为,以下论点支持“单一责任原则”是一种良好做法的主张。我还提供了指向更多文献的链接,在这些文献中您可以阅读甚至更详细的论据-并且比我的论断更雄辩:

  • 更好的维护:理想情况下,每当必须更改系统功能时,都将只有一个类需要更改。类和职责之间的明确映射意味着项目中涉及的任何开发人员都可以识别出这是哪个类。(如@MaciejChałapuk所指出的,请参见Robert C. Martin,“清洁代码”。)

  • 更简单的测试:理想情况下,类应具有尽可能少的公共接口,并且测试应仅针对该公共接口。如果由于类的许多部分是私有的而无法进行清晰的测试,则这明确表明您的类承担了太多的责任,因此您应该将其拆分为较小的子类。请注意,这也适用于没有“公共”或“私人”班级成员的语言;具有小的公共接口意味着对于客户端代码而言,很清楚应该使用该类的哪一部分。(有关更多详细信息请参见Kent Beck,“测试驱动开发”。)

  • 健壮的代码:您的代码不会因为编写得当而经常失败。但是,作为所有代码,其最终目标不是与机器通信,而是与其他开发人员通信(请参见Kent Beck,“实现模式”,第1章。)清晰的代码库更易于推论,因此将引入更少的错误。 ,在发现错误和修复错误之间将花费更少的时间。


如果由于业务原因需要更改某些东西,请相信我的SRP,您将不得不修改多个类。说如果一个班级发生变化,仅是出于一个原因,那是不一样的,如果发生更改,它只会影响一个班级。
Bastien Vandamme 2014年

@ B413:我并不是说任何更改都意味着对单个类进行单个更改。系统的许多部分可能需要更改才能符合单个业务更改。但是也许您有一点要说,我应该写过“每当系统功能需要更改时”。
logc

3

原因有很多,但是我喜欢的一个原因是许多早期UNIX程序使用的方法:做好一件事。一件事情很难做到,而尝试做更多的事情就变得越来越困难。

另一个原因是限制和控制副作用。我喜欢组合式咖啡壶开门器。不幸的是,当我有访客时,咖啡通常会溢出。前几天煮咖啡后,我忘了关门,有人偷了。

从心理角度来看,您一次只能跟踪几件事。一般估计是七个正负两个。如果一个班级做了很多事情,您需要一次跟踪所有这些。这会降低您跟踪自己正在做的事情的能力。如果一个班级要做三件事,而您只想要其中的三件事,那么您可能会在实际对班级做任何事情之前就用尽能力来跟踪事情。

进行多项操作会增加代码的复杂性。除了最简单的代码,复杂性的增加还增加了发生错误的可能性。从这个角度出发,您希望类尽可能简单。

测试一个做一件事的类要简单得多。您不必验证每次测试该类所做的第二件事是否发生。您也不必修复损坏的条件并在其中一项测试失败时重新测试。


2

因为软件是有机的。需求不断变化,因此您必须尽可能少地操作组件。通过不遵循SOLID原则,您可能最终得到了具体设置的代码库。

想象一栋带有承重混凝土墙的房屋。如果您在没有任何支持的情况下拆除隔离墙,会发生什么?房子可能会倒塌。在软件中,我们不需要这样做,因此我们以一种方式来构造应用程序,以便您可以轻松移动/替换/修改组件而不会造成很多损坏。


1

我遵循以下想法:1 Class = 1 Job

使用生理学类比:运动(神经系统),呼吸(肺),消化(胃),嗅觉(观察)等。这些控制器中的每一个都有一个子集,但无论它们是否要管理,它们都只有一个职责它们各自子系统的工作方式或它们是否是端点子系统并且仅执行一项任务,例如抬起手指或生长毛囊。

不要混淆它可能充当经理而不是工人的事实。当一些工人执行的工作变得过于复杂以至于一个流程无法自行处理时,他们最终会被提升为经理。

我经历过的最复杂的部分是,知道何时指定一个班级为操作员,主管或经理程序。无论如何,您需要观察并表示其功能以承担1种责任(操作员,主管或经理)。

当一个类/对象执行多个上述类型角色时,您会发现整个过程将开始出现性能问题或过程瓶颈。


即使应该将处理大气中的氧气转化为血红蛋白的实现作为一个Lung类来处理,我也会假设Person仍然应该有一个实例Breathe,因此Person该类应包含足够的逻辑以至少委派与该方法相关的责任。我进一步建议,与具有多个独立访问点的林相比,只有通过共同所有者才能访问的互连实体林通常更容易推理。
supercat 2014年

是的,在这种情况下,肺是经理,尽管绒毛将是主管,呼吸的过程将是工人班,以将空气颗粒转移到血液中。
GoldBishop 2014年

@GoldBishop:也许正确的观点是说,一个Person类的职责是委派与一个极其复杂的“现实世界”实体相关的所有功能,包括其各个部分的关联。让一个实体执行500件事是很丑陋的,但是如果这是要模拟的现实世界系统的工作方式,那么拥有一个可以委派500个功能同时保留一个身份的类可能比必须处理所有不相交的部分要好。
supercat 2014年

@supercat或者您可以简单地对整个过程进行分区,并在子流程中具有1个带有必要监督者的类。这样,您可以(理论上)使每个过程都与基本类分开,Person但仍会在成功/失败方面报告整个过程,但不一定会影响其他过程。500种功能(尽管以示例为例,但会有些过时且不被支持),我试图保持这种类型的功能派生和模块化。
GoldBishop 2014年

@GoldBishop:我希望有某种方法可以声明“临时”类型,以便外部代码可以在其上调用成员,也可以将其作为参数传递给该类型自己的方法,但除此之外别无其他。从代码组织的角度来看,细分类是有道理的,甚至可以将外部代码调用方法下一层(例如,Fred.vision.lookAt(George)是有道理的,但是允许代码说起来someEyes = Fred.vision; someTubes = Fred.digestion似乎很棘手,因为它掩盖了someEyes和之间的关系someTubes。)
supercat

1

我个人尤其希望采用“单一责任”这一重要原则,而人们采纳这一原则有很多原因。

其中一些原因可能是:

  • 维护-SRP确保一类职责的改变不会影响其他职责,从而使维护工作变得更加简单。这是因为,如果每个类别只有一个职责,则对一个职责所做的更改与其他职责是隔离的。
  • 测试-如果一个班级有一个责任,那么就很容易弄清楚如何测试这一责任。如果班级有多个职责,则必须确保您正在测试正确的职责,并且该测试不受班级所承担的其他职责的影响。

另请注意,SRP附带了SOLID原理包。遵守SRP而忽略其余部分与不进行SRP一样糟糕。因此,您不应单独评估SRP,而应在所有SOLID原则的背景下进行评估。


... test is not affected by other responsibilities the class has,请您详细说明一下吗?
Mahdi 2014年

1

理解这些原则的重要性的最好方法是有需要。

当我还是一名新手程序员时,我并没有对设计进行太多思考,实际上,我什至不知道设计模式是否存在。随着我的程序的发展,改变一件事意味着改变许多其他事情。很难发现错误,代码庞大且重复。没有太多的对象层次结构,到处都是东西。 添加新的东西或删除旧的东西会在程序的其他部分带来错误。去搞清楚。

在小型项目中,这可能并不重要,但是在大型项目中,情况可能会非常糟糕。后来,当我遇到设计模式的概念时,我对自己说:“哦,是的,这样做会使事情变得容易得多”。

在需求出现之前,您真的无法理解设计模式的重要性。我尊重这些模式,因为根据经验我可以说它们使代码维护变得容易并且代码健壮。

但是,就像您一样,我仍然对“简单测试”不确定,因为我还不需要进行单元测试。


模式以某种方式连接到SRP,但在应用设计模式时不需要SRP。可以使用模式而完全忽略SRP。我们使用模式来解决非功能性需求,但是在任何程序中都不需要使用模式。原则对于减轻发展的痛苦至关重要,因此(IMO)应该成为一种习惯。
MaciejChałapuk2014年

我完全同意你的看法。SRP在某种程度上可以强制执行者清理对象设计和层次结构。对于开发人员来说,这是一种很好的心态,因为它使开发人员开始思考对象以及对象的方式。就个人而言,它也对我的发展产生了非常好的影响。
harsimranb 2014年

1

答案是,正如其他人指出的那样,它们全部都是正确的,而且它们相互融合,易于测试,使维护更容易,使代码更健壮,使维护更容易,等等。

所有这些都归结为一个关键的主体-代码应该尽可能的小,并尽可能少地完成工作。这不仅适用于功能,还适用于应用程序,文件或类。一段代码做的事情越多,就越难理解,维护,扩展或测试。

我认为可以用一个词来概括:范围。密切注意工件的范围,在应用程序中任何特定点范围内的事物越少越好。

扩展的范围=更多的复杂性=出错的更多方法。


1

如果一个班级太大,将很难维护,测试和理解,其他答案已经涵盖了这个意愿。

一个班级可能有多个责任而没有问题,但是很快您就会遇到过于复杂的班级的问题。

但是,只要有一个简单的规则“仅一个责任”,就可以更轻松地知道何时需要一门新课

但是,定义“责任”很困难,并不意味着“按照应用程序规范的要求做所有事情”,真正的技能是知道如何将问题分解为“责任”的小单位。

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.