显然,“单一责任原则”并不意味着“只做一件事情”。那就是方法的目的。

public Interface CustomerCRUD
{
    public void Create(Customer customer);
    public Customer Read(int CustomerID);
    public void Update(Customer customer);
    public void Delete(int CustomerID);
}


鲍勃·马丁说:“类应该只有一个改变的理由。”但是,如果您是SOLID的新手,那很难让您想起这个主意。用餐厅的比喻来说明我的观点。但这仍然没有阐明人们可以用来定义班级职责的一套原则。

那么你怎么做呢?您如何确定每个班级应承担的职责,以及如何在SRP的背景下定义职责?

评论

发布到代码审查并撕开:-D

@JörgWMittag嘿,现在,不要吓people人:)

资深会员提出的类似问题表明,我们试图坚持的规则和原则绝非简单明了。就像任何好的规则集一样,它们[自相矛盾且神秘”。而且,我想相信这样的问题对智者谦虚,并给那些感到绝望的愚蠢者带来希望。谢谢罗伯特!

几周以来,我一直在犹豫地提出一个反问题:“有什么线索,一个人将太多的课程分配给一个班级?我相信回答这样的问题也可能对回答OP的问题有用。

@rmunn:换句话说-大代表吸引了更多的代表,因为没有人取消关于stackexchange的基本偏见。

#1 楼

解决这个问题的一种方法是想象未来项目中潜在的需求变更,并问自己要实现这些需求您将需要做什么。
例如:

新的业务需求:位于加利福尼亚的用户可以享受特殊折扣。
“好”更改的示例:我需要在计算折扣的类中修改代码。
差的示例:我需要在User类中修改代码,并且此更改将对使用User类的其他类(包括与折扣无关的类)产生级联影响。注册,枚举和管理。

或:

新的非功能性要求:我们将开始使用Oracle代替SQL Server
好的更改示例:只需要修改数据访问层中的单个类,该类确定如何在DTO中保留数据。 >
想法是最大程度地减少将来潜在更改的占用空间,将代码修改限制在每个更改区域中一个代码区域。
至少,您的类应将逻辑关注点与物理关注点分开。在System.IO命名空间中可以找到很多示例:在这里我们可以找到各种在逻辑级别上工作的物理流(例如FileStreamMemoryStreamNetworkStream)以及各种读取器和写入器(BinaryWriterTextWriter)。通过这种方式将它们分开,我们避免了组合爆炸:不需要FileStreamTextWriterFileStreamBinaryWriterNetworkStreamTextWriterNetworkStreamBinaryWriterMemoryStreamTextWriterMemoryStreamBinaryWriter,您只需连接编写器和流,便可以拥有所需的内容。然后,稍后我们可以添加一个XmlWriter,而无需分别针对内存,文件和网络重新实现它。

评论


尽管我同意未来的想法,但存在YAGNI之类的原理,而TDD thar之类的方法则提出了相反的建议。

–罗伯特·哈维(Robert Harvey)
17 Mar 27 '17 at 19:59

YAGNI告诉我们不要制造我们今天不需要的东西。它没有告诉我们不要以可扩展的方式构建东西。另请参见“打开/关闭”原则,该原则指出“软件实体(类,模块,功能等)应为扩展而开放,但为修改而封闭”。

–吴宗宪
17 Mar 27 '17 20:26



@JohnW:仅对您的YAGNI评论+1。我不敢相信我必须向人们解释,YAGNI并不是建立不能对变化做出反应的僵化,僵化的系统的借口-具有讽刺意味的是,这与SRP和开放/封闭原则的目的正好相反。

– Greg Burghardt
17 Mar 27 '17 at 20:30

@JohnWu:我不同意,YAGNI告诉我们完全不要制造我们今天不需要的东西。例如,可读性和测试是程序始终“需要”的东西,因此YAGNI从来都不是不添加结构和注入点的借口。但是,一旦“可扩展性”增加了可观的成本,而“今天”所带来的好处并不明显,则YAGNI意味着避免这种可扩展性,因为后者会导致工程过度。

–布朗博士
17 Mar 27 '17 at 21:18

@JohnWu我们确实从SQL 2008切换到了2012。总共有两个查询需要更改。以及从SQL Auth到受信任的?为什么还要更改代码?更改配置文件中的connectionString就足够了。再次,YAGNI。 YAGNI和SRP有时是相互竞争的问题,您需要判断哪个具有更好的成本/收益。

–安迪
17 Mar 27 '17 21:23



#2 楼

实际上,责任受那些可能会改变的事物的束缚。因此,不幸的是,没有科学或公式化的方法可以得出构成责任的内容。这是一个判断电话。

这是根据您的经验可能会发生变化的情况。愤怒。我们倾向于对类进行拆分,因为它们可能会发生变化,或者按照简单的路线来帮助我们分解问题。 (后一个原因并非天生就不好。)但是,SRP并不是出于自身的原因而存在的;而是出于自身原因而存在的。

因此,再次说明,如果划分不是由可能的变化驱动的,那么如果YAGNI更适用,它们就不会真正为SRP1服务。两者都有相同的最终目标。两者都是判断的问题-很有经验的判断。换句话说,我们不希望甲方失去工作,因为乙方要求更改。


编写软件模块时,您要确保<要求进行更改,这些更改只能源于单个
人员,或者更确切地说,是一组紧密耦合的人员
,它们代表单个狭窄定义的业务功能。您要
将模块与整个组织的复杂性隔离开,
,并设计系统,使每个模块负责任(响应)仅一个业务功能的需求。 (鲍勃叔叔-单一责任原则



优秀且经验丰富的开发人员将对可能发生的变化有所了解。而且这个心理清单会因行业和组织而有所不同。

在您的特定应用程序中,在您的特定组织中构成责任的,最终取决于经验丰富的判断。关于可能发生的变化。从某种意义上讲,它与谁拥有模块的内部逻辑有关。


1。需要明确的是,这并不意味着它们是糟糕的部门。它们可能是可以极大地提高代码可读性的部门。这只是意味着它们不受SRP的驱动。

评论


最佳答案,实际上引用了Bob叔叔的想法。至于可能发生的变化,每个人都在I / O上做了大量工作,“如果我们更改数据库该怎么办?”或“如果从XML转换为JSON会怎样?”我认为这通常是错误的。真正的问题应该是“如果我们需要将此int更改为float,添加一个字段,并将此String更改为字符串列表,该怎么办?”

–user949300
17-3-28在1:07



这是作弊。单一责任本身只是“变更隔离”的一种提议方式。说明,您需要隔离更改以使责任“单一”,这并不建议如何执行,仅说明要求的由来。

–巴西
17年3月28日在12:40



@Basilevs我正在努力磨练您在此答案中看到的缺陷-更不用说Bob叔叔的答案了!但是,也许我需要澄清一下,SRP并不是要确保“更改”仅影响1类。关于确保每个班级仅对“一个更改”做出响应。 ...这是关于尝试从每个班级向单个所有者绘制箭头。不是从每个所有者到单个班级。

– svidgen
17年3月28日在13:50

感谢您提供务实的回应!甚至Bob叔叔也警告不要热衷于敏捷架构中的SOLID原则。我没有引号,但是他基本上说,分担职责会固有地增加代码中的抽象级别,并且所有抽象都是有代价的,因此请确保遵循SRP(或其他原则)的好处大于成本添加更多抽象。 (续下一条评论)

– Michael L.
17 Mar 29 '17 at 15:16

这就是为什么我们应该尽早并尽可能合理地将产品展示在客户面前,这样他们将迫使我们的设计发生变化,并且我们可以看到该产品可能在哪些领域发生变化。此外,他警告说,我们不能保护自己免受各种变化的影响。对于任何应用程序,将很难进行某些类型的更改。我们需要确保这些是最不可能发生的更改。

– Michael L.
17 Mar 29 '17 at 15:16

#3 楼

我遵循“班级应该只有一个改变的理由”。

对我来说,这意味着思考产品所有者可能想出的繁琐方案(“我们需要支持移动!”,“我们需要上云!”,“我们需要支持中文!”。好的设计将把这些方案的影响限制在较小的范围内,并使它们相对容易实现。错误的设计意味着要花费大量代码并进行大量风险更改。

我发现唯一能正确评估这些疯狂方案可能性的经验-因为简单地做可能会使另外两个难度更大-并评估设计的优劣。经验丰富的程序员可以想象他们需要做些什么来更改代码,躺在床上如何咬它们,以及什么技巧使事情变得容易。经验丰富的程序员对产品所有者索要疯狂东西时的想法感到很困惑。

实际上,我发现单元测试在这里有所帮助。如果您的代码不灵活,将很难进行测试。如果无法注入模拟或其他测试数据,则可能无法注入该SupportChinese代码。

另一个粗略的度量标准是电梯间距。传统的电梯推销方式是“如果您与投资者一起乘坐电梯,您能以一个想法出售他吗?”。初创企业需要对自己的工作-重点是什么做一个简短的简短描述。同样,类(和函数)应具有对其作用的简单描述。不是“该类实现了一些附加功能,因此您可以在这些特定情况下使用它”。您可以告诉其他开发人员:“此类创建用户”。如果您无法与其他开发人员进行交流,那么您将遇到错误。

评论


有时您去实现您认为是一团糟的更改,结果变得很简单,或者小的重构使之变得简单,并同时添加了有用的功能。但是,是的,通常您会发现麻烦来了。

–user251748
17 Mar 28 '14:24



我是“电梯沥青”理念的大力倡导者。如果很难用一两句话来解释一个班级的表现,那您就处于危险境地。

–伊凡
17 Mar 28 '17 at 14:55

您谈到了一个重要的观点:这些疯狂计划的可能性从一个项目所有者到下一个项目所有者都大不相同。您不仅要依赖于您的一般经验,还必须取决于您对项目所有者的了解程度。我为那些希望将我们的冲刺减少到一周的人工作,但仍然无法避免在冲刺中改变方向。

–凯文·克鲁姆维德(Kevin Krumwiede)
17年3月28日在16:41

除了明显的好处之外,使用“电梯音调”对代码进行文档记录还可以帮助您使用自然语言来思考您的代码在做什么,我发现这种语言对于发现多种职责很有用。

–亚历山大
17 Mar 28 '17 at 19:28

@KevinKrumwiede这就是“鸡头被砍断跑来跑去”和“野鹅追逐”方法学的目的!

–user251748
17年3月28日在23:00

#4 楼

没人知道。或至少,我们无法就一个定义达成共识。这就是使SPR(和其他SOLID原则)引起很大争议的原因。他的职业生涯。您编写和查看的代码越多,确定某项是单个或多个职责的经验就越丰富。或者,如果单一责任分散在代码的不同部分中。

我认为SRP的主要目的不是硬性规定。这是在提醒我们注意代码中的内聚性,并始终在确定哪些代码具有内聚性和什么不是内聚性方面付出一些自觉的努力。

评论


新程序员确实倾向于将SOLID视为一套法律,而事实并非如此。这只是一组好主意,可以帮助人们在课堂设计中变得更好。 people,人们往往过于重视这些原则。我最近看到一份职位公告,其中提到SOLID是职位要求之一。

–罗伯特·哈维(Robert Harvey)
17-3-27在22:40



最后一段+42。正如@RobertHarvey所说,诸如SPR,SOLID和YAGNI之类的东西不应被视为“绝对规则”,而应被视为“良好建议”的一般原则。他们之间(和其他人)的建议有时会矛盾,但是平衡建议(而不是遵循严格的规则)将(随着时间的流逝,随着经验的增长)将指导您开发更好的软件。在软件开发中应该只有一个“绝对规则”:“没有绝对规则”。

– TripeHound
17年3月28日在11:11

这是对SRP的一个很好的说明。但是,即使SOLID原则不是硬性规定,如果没有人理解它们的含义,它们也就不是很有价值-甚至如果您声称“没人知道”的说法是正确的,它们也就不那么有价值了! ...让他们很难理解是有道理的。与任何技能一样,有些东西可以区分善与恶!但是……“没人知道”使它更像是一种令人讨厌的仪式。 (而且我不认为这是SOLID的意图!)

– svidgen
17年3月28日14:10



通过“没人知道”,我希望@Euphoric只是意味着没有精确的定义适用于每个用例。这需要一定的判断力。我认为确定职责所在的最佳方法之一是快速迭代并让您的代码库告诉您。寻找“气味”,指出您的代码不容易维护。例如,当对单个业务规则的更改开始对看似无关的类产生级联影响时,您可能违反了SRP。

– Michael L.
17 Mar 29 '17在15:29

我衷心地赞同@TripeHound和其他指出第二个“规则”的人,并不存在定义开发的“真正宗教”,而是增加了开发可维护软件的可能性。如果您无法解释“最佳实践”是如何促进可维护软件,提高质量或提高开发效率的,则应特别小心。

– Michael L.
17 Mar 29 '17 at 15:32

#5 楼

我认为“责任”一词可以用作隐喻,因为它使我们能够使用该软件来调查软件的组织程度。特别是,我将重点关注两个原则:


责任与权力相对应。
没有两个实体应对同一件事负责。

这两个原则使我们有意义地减轻了责任,因为它们相互抵消。如果您要授权一段代码为您做某事,那么它需要对所做的事情负责。这导致班级可能必须增长的责任,将其“改变的一个理由”扩展到越来越广泛的范围。但是,当您扩大范围时,您自然会开始遇到多个实体负责同一件事的情况。这充满了现实生活中的责任问题,因此,毫无疑问,这也是编码中的问题。结果,当您将责任细分为无重复的宗地时,此原则会导致范围缩小。
可以委派职责

考虑一个新创建的程序...一块空白的板子。首先,您只有一个实体,这是程序的整体。它负责一切。自然,在某个时候,您将开始将职责委派给函数或类。至此,前两个规则开始起作用,迫使您平衡这种责任。高层计划仍然负责总体输出,就像经理负责团队的生产力一样,但是每个子实体都被委以责任,并有权执行该责任。

另外,这使SOLID与可能需要执行的任何公司软件开发特别兼容。地球上的每个公司都有一些关于如何下放责任的概念,他们并不完全同意。如果您以类似于公司自己的委托的方式在软件中委派职责,那么将来的开发人员就可以更轻松地掌握如何在该公司处事。

评论


我不是100%肯定会完全说明这一点。但是,我认为对“权威”进行解释“责任”是一种有见地的表达方式! (+1)

– svidgen
17年3月28日在21:21



皮尔西格说:“您倾向于将问题植入机器中”,这让我停下来。

–user251748
17 Mar 28 '17在23:03

@nocomprende您也倾向于在机器中发挥自己的优势。我认为,当你的优点和缺点相同时,就会变得很有趣。

–Cort Ammon
17 Mar 28 '17在23:13

#6 楼

在耶鲁大学的这次会议上,鲍伯叔叔举了一个有趣的例子:


和嘲讽,但仍然是说明性的:



如果Employee方法出错并导致公司损失数百万美元,则CFO将开除您。
如果CalcPay()方法出错并给公司造成数百万美元的损失,则首席运营官将解雇您。
如果ReportHours())方法出错会导致擦除大量数据和成本公司成千上万美元,
CTO将解雇您。
职责太多。


他提供了解决违反SRP的解决方案,但必须解决视频中未显示的DIP违反。 br />


评论


该示例看起来更像是一个职责不正确的类。

–罗伯特·哈维(Robert Harvey)
17 Mar 28 '17 at 23:50

@RobertHarvey当类的职责过多时,这意味着额外的职责是错误的职责。

–图兰斯·科尔多瓦(TulainsCórdova)
17 Mar 28 '17在23:54

我听到了你在说什么,但我认为它没有说服力。一个班级承担太多责任,而一个班级做一些根本没有生意的事,这是有区别的。听起来可能一样,但事实并非如此。数花生不等于称核桃。这是鲍伯叔叔的原则和鲍伯叔叔的例子,但是如果它具有足够的描述性,我们根本就不需要这个问题。

–罗伯特·哈维(Robert Harvey)
17-3-28在23:57



@RobertHarvey,有什么区别?在我看来,那些情况是同构的。

– Paul Draper
17年4月1日在18:45

#7 楼

我认为,比“更改原因”更好地细分事物的方法是,首先考虑是否需要执行两个(或多个)动作的代码应该持有一个单独的对象引用是否有意义对于每个动作,以及拥有可以执行一个动作但不能执行另一个动作的公共对象是否有用。通过单独的类。如果对这两个问题的回答均否,则表明从公众的角度来看应该有一个阶级。如果该代码繁琐,则可以在内部将其细分为私有类。如果第一个问题的答案为“否”,而第二个问题的答案为“是”,则每个动作应有一个单独的类,再加上一个包含对其他实例的引用的复合类。

收银机的键盘,蜂鸣器,数字读出器,收据打印机和收银机的类,而没有完整的收银机的复合类,则本应用于处理交易的代码可能最终会意外地以需要输入的方式被调用从一台机器的键盘上发出信号,从第二台机器的蜂鸣器发出噪音,在第三台机器的显示屏上显示数字,在第四台机器的打印机上打印收据,然后弹出第五台机器的现金抽屉。这些子函数中的每个子函数可能有用地由一个单独的类处理,但是也应该有一个将它们连接在一起的复合类。组合类应将尽可能多的逻辑委托给组成类,但应在实际包装其组成组件的功能时,而不是要求客户端代码直接访问组成部分。可以说每个类的“职责”要么是合并一些实际的逻辑,要么是为其他多个类提供一个公共的附加点,但是重要的是首先重点关注客户端代码应如何查看一个类。如果让客户端代码将某些内容视为单个对象有意义,那么客户端代码应将其视为单个对象。

评论


这是合理的建议。可能值得指出的是,您不仅根据SRP,还根据更多标准来划分职责。

–Jørgen Fogh
17年3月28日在12:18

打车类比:我不需要知道别人的油箱里有多少汽油,也不需要打开别人的雨刷器。 (但这就是互联网的定义)(嘘!您会毁了这个故事)

–user251748
17 Mar 28 '17 at 14:29

@nocomprende-“我不需要知道别人的油箱中有多少汽油,”-除非您是一个十几岁的少年,试图决定为下次旅行“借用”家庭的哪辆车...;)

–alephzero
17 Mar 30 '17 at 11:33

#8 楼

SRP很难正确解决。这主要是为代码分配“工作”,并确保每个部分都有明确的职责。就像在现实生活中一样,在某些情况下将工作分配给人们是很自然的,但在另一些情况下,这可能确实很棘手,尤其是在您不认识他们(或工作)的情况下。总是建议您先编写简单的代码,然后再进行一些重构:一段时间后,您将趋向于看代码是如何自然聚集的。我认为在了解代码(或人员)和要完成的工作之前强加责任是错误的。

您会注意到的一件事是,模块开始做太多事情并且很难执行时调试/维护。这是重构的时刻。核心工作应该是什么?可以将什么任务交给另一个模块?例如,它应该处理安全检查和其他工作,还是应该先在其他地方进行安全检查,否则这会使代码更复杂吗?

使用太多的间接访问,这又会变得一团糟。 ..至于其他原则,这将与其他原则(例如KISS,YAGNI等)发生冲突。一切都是平衡问题。

评论


SRP不仅仅是君士坦丁的凝聚力书吗?

–尼克·基利(Nick Keighley)
17 Mar 28 '17 at 10:45

如果您编码足够长的时间,您自然会发现这些模式,但是您可以通过命名它们来加快学习速度,并且有助于进行交流...

–克里斯托弗·鲁西(Christophe Roussy)
17 Mar 28 '17 at 12:25

@NickKeighley我认为这是凝聚力,不是那么大的命令,而是从另一个角度来看的。

–斯登纳姆
17 Mar 29 '17 at 11:23

#9 楼

“单一责任原则”也许是一个令人困惑的名称。 “只有改变的理由”是对原理的更好描述,但仍然容易被误解。我们不是在说什么导致对象在运行时更改状态。我们正在考虑将来可能导致开发人员更改代码的原因。
除非我们修复了一个错误,否则更改将是由于新的或更改的业务需求而引起的。您将不得不在代码本身之外进行思考,并想象哪些外部因素可能导致需求独立更改。说:


由于政治决定而改变了税率。
市场营销决定更改所有产品的名称
用户界面必须重新设计以方便使用
数据库很拥挤,因此您需要进行一些优化
您必须容纳一个移动应用程序
等等...

理想情况下,您希望独立的因素影响不同的应用类。例如。由于税率的更改与产品名称无关,因此更改不应影响相同的类别。否则,您将面临引入税收变更的风险,即产品命名中的错误,这是您希望通过模块化系统避免的紧耦合。变化-将来可能发生任何变化。专注于可能独立改变的地方。如果更改是由不同的行为者引起的,则更改通常是独立的。

您的职位示例在正确的轨道上,但是您应该更实际地理解它!如果营销可能导致代码更改而财务可能导致其他更改,则这些更改不应影响同一代码,因为这些职位实际上是不同的职位,因此更改将独立发生。

引用Bob叔叔谁发明了术语:


编写软件模块时,您要确保在请求
更改时,这些更改只能源自单个
人,或者说是紧密联系在一起的一群人,代表一个狭义的业务职能。您想
将模块与整个组织的复杂性隔离开,
,并设计系统,使每个模块负责任(响应)仅一个业务功能的需求。


总而言之:“责任”是满足单个业务功能的需求。如果一个以上的演员可能导致您不得不更换班级,那么该班级可能违反了这一原则。

评论


根据他的《清洁建筑》一书,这是完全正确的。业务规则应该来自一个来源,并且只能来自一个来源。这意味着人力资源,运营和IT部门都需要在“单一职责”中合作制定要求。这就是原则。 +1

– Benny Skogberg
18年7月23日在19:46

#10 楼

https://scotch.io/bar-talk/solid-the-first-five-principles-of-object-iented-设计。在与SRP有关的示例中,他给出了一些形状类(圆形和正方形)的示例,以及一个用于计算多个形状的总面积的类。

在他的第一个示例中,他创建了面积计算类,并将其输出返回为HTML。后来,他决定将其改为显示为JSON,并且必须更改其面积计算类。

这个示例的问题在于,他的面积计算类负责计算形状的面积并显示该面积。然后,他使用另一个专门用于显示区域的类通过一个更好的方法来实现此目的。 SRP的想法。

#11 楼

首先,实际上您有两个独立的问题:在类中使用什么方法的问题以及接口膨胀的问题。

接口

此接口:

public Interface CustomerCRUD
{
  public void Create(Customer customer);
  public Customer Read(int CustomerID);
  public void Update(Customer customer);
  public void Delete(int CustomerID);
}


大概是,您有多个符合CustomerCRUD接口的类(否则就不需要一个接口),而某些函数do_crud(customer: CustomerCRUD)则包含一个符合对象。但是您已经打破了SRP:您已经将这四个不同的操作捆绑在一起了。数据库视图仅具有Read方法可用。但是您想编写一个函数do_query_stuff(customer: ???),该函数可以在完整的表或视图上透明地进行操作;毕竟,它仅使用Read方法。

所以请创建一个接口

public interface CustomerReader
{
}

CustomerCrud的接口分解为:可能有一些我们可以创建但不能更新的对象,等等。这个兔子洞太深了。遵守单一责任原则的唯一明智的方法是使您的所有接口都完全包含一种方法。 Go实际上是根据我所见的方法遵循的,大多数接口都包含一个函数。如果要指定一个包含两个函数的接口,则必须笨拙地创建一个将两个函数结合在一起的新接口。您很快就会看到接口的组合爆炸。我们没有定义接口。相反,我们可以简单地编写一个函数

public interface CustomerCRUD extends CustomerReader
{
  public void Create(Customer customer);
  public void Update(Customer customer);
  public void Delete(int CustomerID);
}


调用我们喜欢的任何方法。 OCaml将使用类型推断来确定我们可以传入实现这些方法的任何对象。在此示例中,将确定customer的类型为<read: int -> unit, update: int -> unit, ...>



这解决了接口混乱;但是我们仍然必须实现包含多个方法的类。例如,我们应该创建两个不同的类CustomerReaderCustomerWriter吗?如果我们想更改读取表的方式(例如,现在我们在获取数据之前将响应缓存在redis中),但是现在如何写入它们呢?如果遵循这个推理链得出其逻辑结论,那么您将不可避免地导致函数式编程:)

评论


“无意义”有点强。我可以落后于“神秘主义者”或“禅宗”。但是,并非毫无意义!

– svidgen
17 Mar 27 '17 at 22:18

您能否再解释一下为什么结构子类型化是一种解决方案?

–罗伯特·哈维(Robert Harvey)
17 Mar 27 '17 at 23:04

@RobertHarvey大大重组了我的答案

– Gardenhead
17年3月28日在1:19

即使只有一个类来实现它,我也使用接口。为什么?模拟单元测试。

–永恒21
17年3月28日在18:31

#12 楼

在我看来,最接近SRP的是使用流程。如果您没有任何给定类的明确使用流程,则可能是您的类具有设计气味。 )结果。您基本上是使用IMHO的用例定义一个类,因此所有程序方法都将重点放在实现而非实现上。

#13 楼

这是要实现多个需求更改,而不需要更改组件。
但是幸运的是,乍一看,当您第一次了解SOLID时。有评论说SRP和YAGNI可能彼此矛盾,但是TDD(伦敦学校,GOOS)强制执行的YAGNI教会了我从客户的角度考虑和设计组件的想法。我开始通过客户端希望它做的最少的事情来设计我的界面,这就是应该做的事。而且可以在没有TDD知识的情况下完成该练习。该课程的目的是什么?
您的答案是否包含And或Or
如果是,请提取答案的那一部分,这是它自己的责任

该技术是正如@svidgen所说,SRP是一个判断电话,但是当学习新东西时,绝对值是最好的,总是执行某些操作会更容易。确保不分开的原因是:有根据的估算,而不是因为您不知道如何做。这是一门艺术,它需要经验。

在谈论SRP时,我认为很多答案似乎都在说明去耦。从理论上讲,没有传播就没有依赖关系图。
从理论上讲,没有SRP,您将没有任何依赖关系。那。但是,SRP确实改善了开放式封闭原则。该原理更多地是关于抽象的,但是,较小的抽象更易于实现。
因此,在整体教授SOLID时,请谨慎教导,当需求变化时SRP允许您更改较少的代码,而实际上,它允许您无需编写新代码。

评论


当学习新东西时,绝对是最好的,总是做某事比较容易。 -以我的经验,新程序员太教条了。专制主义导致了没有思想的开发人员和不拘一格的编程。只要您了解要与之交谈的人以后必须不学习您教给他们的内容,那么说“做到这一点”就可以了。

–罗伯特·哈维(Robert Harvey)
17 Mar 29 '17 at 14:45



@RobertHarvey,完全正确,它会产生教条式的行为,您在获得经验时必须取消学习。这是我的观点。如果一个新的程序员试图进行判断调用而没有任何理由来推理他们的决定,那么似乎边界就没有用了,因为他们不知道它为什么起作用,何时起作用。通过让人们过度使用它,它教会他们寻找例外,而不是做出无条件的猜测。您所说的关于专制主义的一切都是正确的,这就是为什么它仅应作为起点的原因。

–克里斯·沃勒特(Chris Wohlert)
17-3-30在12:23



@RobertHarvey,一个快速的现实生活示例:您可能会教给孩子永远诚实,但是随着他们长大,他们可能会意识到一些人们不想听他们最诚实的想法的例外情况。期望一个5岁的孩子对诚实做正确的判断最好是乐观的。 :)

–克里斯·沃勒特(Chris Wohlert)
17-3-30在12:26



#14 楼

没有明确的答案。尽管问题很狭窄,但解释不是。

如果我愿意,它就像是Occam的Razor。这是我尝试评估当前代码的理想选择。很难用简单明了的话将其确定下来。另一个比喻是“一个主题”,它与“单一责任”一样抽象,即难以理解。第三个描述是»处理一个抽象级别«。

这实际上是什么意思?

第一阶段最好形容为创造性的混乱。在这一阶段中,我会随着思想的流逝写下代码-即原始且丑陋。

第二阶段完全相反。就像飓风过后清理一样。这需要最多的工作和纪律。然后,我从设计者的角度来看代码。

我现在主要在Python中工作,这使我以后可以想到对象和类。第一阶段-我只编写函数,并且几乎随机地将它们分散在不同的模块中。在第二阶段中,当我做好准备后,我将仔细研究哪个模块处理解决方案的哪一部分。在浏览这些模块时,主题浮现出来。一些功能在主题上相关。这些是上课的好人选。在将函数转换为类之后-几乎完成了缩进,并将self添加到python中的参数列表中。当前的示例可能是前几天编写了小的导出功能。

需要使用CSV和excel以及合并的excel表格进行压缩。

普通功能分别在三个视图(=功能)中完成。
每个函数都使用用于确定过滤器的通用方法和用于检索数据的第二种方法。然后在每个函数中,准备导出并作为服务器的响应来传递。

混合了太多的抽象级别:

I)处理具有传入/传出的请求/响应

II)确定过滤器

III)检索数据

IV)数据转换

最简单的步骤是第一步使用一个抽象(SRP)处理II-IV层。

剩下的只是处理请求/响应的主题了。
在相同的抽象级别上,可以提取请求参数。因此,对于这种观点,我只有一种“责任”。

其次,我不得不分解出口商,正如我们所看到的那样,出口商至少包括另外三个抽象层。

确定过滤器标准和实际检索几乎处于相同的抽象级别(需要过滤器才能获取数据的正确子集)。这些级别被放入数据访问层之类的东西中。

在下一步中,我将实际的导出机制分开:在需要写入临时文件的地方,我将其分为两个“职责”:一个是将数据实际写入磁盘,另一个是处理实际格式的部分。

随着类和模块的形成,事情变得更清楚了,什么属于哪里。始终是一个潜在的问题,即班级是否做得太多。 br />

很难给出一个食谱。当然,我可以重复神秘的“一个抽象层次”-如果有帮助的规则。

对我来说,最主要的是一种“艺术直觉”,导致了目前的设计;我对代码进行建模,就像艺术家可以雕刻粘土或绘画一样。

想象我是一个编码Bob Ross;)

#15 楼

我尝试编写遵循SRP的代码的方法是:


选择您需要解决的特定问题;编写解决方案的代码,用一种方法编写所有内容(例如:main);
仔细分析代码,并根据业务,尝试定义正在执行的所有操作中可见的职责(这是主观部分,也取决于业务/项目/客户);
请注意,所有功能均已实现;接下来的只是代码的组织(此方法从现在开始将不再实现其他功能或机制);
根据您在先前步骤中定义的职责(根据业务和“一个改变想法的理由),为每个想法分别提取一个单独的类或方法;
请注意,这种方法仅在乎SPR;理想情况下,这里还应该尝试遵循其他原则的其他步骤。

示例:

问题:从用户那里得到两个数字,计算它们的总和并输出给用户的结果:

 //first step: solve the problem right away
static void Main(string[] args)
{
    Console.WriteLine("Number 1: ");
    int firstNumber = Convert.ToInt32(Console.ReadLine());

    Console.WriteLine("Number 2: ");
    int secondNumber = Convert.ToInt32(Console.ReadLine());

    int result = firstNumber + secondNumber;

    Console.WriteLine("Hi there! The result is: {0}", result);

    Console.ReadLine();
}
 


接下来,尝试根据需要的任务定义职责被执行。从中提取适当的类:

 //Responsible for getting two integers from the user
class Input {
    public int FirstNumber { get; set; }
    public int SecondNumber { get; set; }
    public void Read() {
        Console.WriteLine("Number 1: ");
        FirstNumber = Convert.ToInt32(Console.ReadLine());

        Console.WriteLine("Number 2: ");
        SecondNumber = Convert.ToInt32(Console.ReadLine());
    }
}

//Responsible for calculating the sum of two integers
class SumOperation {
    public int Result { get; set; }
    public void Calculate(int a, int b) {
        Result = a + b;
    }
}

//Responsible for the output of some value to the user
class Output {
    public void Write(int result) {
        Console.WriteLine("Hello! The result is: {0}", result);
    }
}
 


然后,重构的程序将变成: br />
 //Program: responsible for main execution.
//Gets two numbers from user and output their sum.
static void Main(string[] args)
{
    var input = new Input();
    input.Read();

    var operation = new SumOperation();
    operation.Calculate(input.FirstNumber, input.SecondNumber);

    var output = new Output();
    output.Write(operation.Result);

    Console.ReadLine();
}
 


注意:这个非常简单的示例仅考虑了SRP原理。其他原则的使用(例如:“ L”-代码应依赖于抽象而不是依赖)将为代码提供更多好处,并使其对于业务变更更具可维护性。

评论


您的示例过于简单,不足以充分说明SRP。在现实生活中没有人会这样做。

–罗伯特·哈维(Robert Harvey)
17-10-4在15:06

是的,在实际项目中,我编写一些伪代码,而不是像示例中那样编写确切的代码。在伪代码之后,我尝试像在示例中一样拆分职责。无论如何,这就是我要做的。

–艾默生·卡多佐(Emerson Cardoso)
17-10-4在16:39

#16 楼

罗伯特·马丁斯(Robert C.Martins)于2017年9月10日出版的《清洁架构:软件结构和设计的技工指南》一书中,罗伯特在第62页上写道:这样,

一个模块应该只有一个更改理由。

为了满足用户和利益相关者而更改了软件系统;这些
用户和利益相关者是“改变的理由”。
原则在谈论。确实,我们可以将原则改写为:


一个模块应该对一个用户(或用户或利益相关者)负责,

“用户”和“利益相关者”实际上并不是在这里使用的正确词。可能会有不止一个用户或利益相关者以理智的方式更改系统。相反,我们实际上是指一组-一个或多个需要更改的人。我们将这个组称为参与者。

因此,SRP的最终版本是:


所以这与代码无关。 SRP是关于控制需求和业务需求流的,这只能来自一个方面。

评论


我不确定您为什么要区分“这与代码无关”。当然,这与代码有关。这是软件开发。

–罗伯特·哈维(Robert Harvey)
18年7月23日在20:15

@RobertHarvey我的观点是需求流来自一个来源,演员。用户和利益相关者不参与代码,他们参与作为需求而来的业务规则。因此,SRP是控制这些需求的过程,对我而言,这不是代码。这是软件开发(!),但不是代码。

– Benny Skogberg
18年7月23日在20:20