您如何定义单一责任?

时间:2008-10-29 07:43:12

标签: oop solid-principles single-responsibility-principle

我知道“班级有一个改变的理由”。现在,到底是什么?是否有一些气味/迹象可以说明该课程没有一个责任?或者,真正的答案是否可以隐藏在YAGNI中,并且只有在您的班级第一次更改时才重构一项责任?

13 个答案:

答案 0 :(得分:24)

单一责任原则

有许多明显的情况,例如CoffeeAndSoupFactory。同一装置中的咖啡和汤会导致非常令人不快的结果。在此示例中,设备可能会分为HotWaterGenerator和某种Stirrer。然后可以从这些组件构建新的CoffeeFactorySoupFactory,并且可以避免任何意外混合。

在更微妙的情况中,数据访问对象(DAO)和数据传输对象(DTO)之间的紧张关系非常普遍。 DAO与数据库通信,DTO可序列化,以便在进程和机器之间进行传输。通常DAO需要引用您的数据库框架,因此它们在富客户端上不可用,它们既没有安装数据库驱动程序,也没有访问数据库所需的权限。

Code Smells

  • 类中的方法开始按功能区域分组(“这些是Coffee方法,这些是Soup方法”。

  • 实施多个接口。

答案 1 :(得分:24)

写一个简短但准确的描述课程的内容。

如果描述中包含单词“and”,则需要将其拆分。

答案 2 :(得分:11)

嗯,这个原则是与一些盐一起使用......以避免类爆炸。

单一责任不会转化为单一方法类。它意味着存在的一个原因......对象为其客户提供的服务。

留在路上的好方法......使用对象作为人物隐喻......如果对象是一个人,我会要求他做这个?将该责任分配给相应的类。但是,您不会要求同一个人执行您的管理文件,计算工资,发放薪水和验证财务记录......为什么您希望单个对象执行所有这些操作? (如果一个类承担多重责任,只要它们都是相关且连贯的就可以了。

  • 如果您使用CRC卡,这是一个很好的细微指南。如果你无法在CRC卡上获得该对象的所有责任,那么它可能做得太多了......最多7个就可以作为一个好的标记。
  • 重构书中的另一个代码味道是巨大的类。霰弹枪手术将是另一个......改变一个类别中的一个区域会导致同一级别的无关区域出现错误......
  • 发现你一次又一次地对同一个类进行无关错误修复的更改,这是该课程做得太多的另一个迹象。

答案 3 :(得分:9)

一种简单实用的方法来检查单一责任(不仅是类而且还有类的方法)是名称选择。当您设计一个类时,如果您轻松找到该类的名称,该名称确切地指定了它所定义的内容,那么您的方法就是正确的。 选择名称的难度几乎总是设计不良的症状。

答案 4 :(得分:5)

您班级中的方法应该具有凝聚力......它们应该协同工作并在内部使用相同的数据结构。如果你发现有太多的方法似乎没有完全相关,或者看起来在不同的事情上运作,那么你很可能没有一个好的单一责任。

通常很难初步找到责任,有时你需要在几个不同的上下文中使用该类,然后在开始看到区别时将类重构为两个类。有时您会发现这是因为您将抽象的具体概念混合在一起。它们往往更难以看到,而且,在不同的背景下使用将有助于澄清。

答案 5 :(得分:5)

显而易见的迹象是,当你的班级最终看起来像Big Ball of Mud时,这实际上与SRP(单一责任原则)相反。

基本上,所有对象的服务应该专注于执行单一责任,这意味着每当你的课程改变并添加一个不尊重它的服务时,你就知道你正在“偏离”“正确”的道路; )

原因通常是由于一些快速修复,匆忙添加到类中修复一些缺陷。因此,更改班级的原因通常是检测您是否要打破SRP的最佳标准。

答案 6 :(得分:2)

也许比其他气味更具技术性:

  • 如果你发现你需要几个“朋友”类或功能,那通常是一个好的SRP气味 - 因为所需的功能实际上并没有被你的班级公开曝光。
  • 如果你最终得到一个过于“深层”的层次结构(一长串的派生类,直到你得到叶子类)或“广泛”的层次结构(很多很多的类从单个父类派生得很浅)。这通常表明父类太多或太少。什么都不做就是限制,是的,我在实践中已经看到,使用“空”父类定义只是为了将一堆不相关的类组合在一个层次结构中。

我还发现重构单一责任很难。当你最终解决这个问题时,类的不同职责将与客户端代码纠缠在一起,这使得很难在没有破坏其他东西的情况下将一件事情分解出来。我宁愿错误地站在“太少”而非“太多”的一边。

答案 7 :(得分:2)

如果您最终使用的MethodA MemberA和使用MethodB的{​​{1}}且是部分并发或版本控制的一部分方案,你可能违反了SRP。

如果你注意到你有一个只是委托调用很多其他类的类,你可能会陷入代理类地狱。如果您最终可以直接在特定类中使用特定类,那么最终会在任何地方实例化代理类,尤其如此。我见过很多这个。将MemberBProgramNameBL类视为使用存储库模式的替代。

答案 8 :(得分:2)

以下是帮助我弄清楚我的班级是否违反SRP的一些事情:

  • 在课堂上填写XML doc评论。如果你使用像if,and,但是,除了,等等,你的类可能做得太多了。
  • 如果您的类是域服务,则名称中应包含动词。很多时候你有像“OrderService”这样的类,它们应该被分解为“GetOrderService”,“SaveOrderService”,“SubmitOrderService”等。

答案 9 :(得分:2)

Martin的Agile Principles, Patterns, and Practices in C#帮助我掌握了SRP。他将SRP定义为:

  

一个班级应该只有一个改变的理由。

那么推动变革的是什么?

马丁的回答是:

  

[...]每项责任都是变革的轴心。 (p。116

并进一步:

  

在战略调整计划的背景下,我们将责任定义为变革的原因。如果你能想到改变一个班级的不止一个动机,那么这个班级就有不止一个责任(p。117

事实上,SRP正在封装变革。如果发生变化,它应该只有本地影响。

YAGNI在哪里?

YAGNI可以很好地与SRP结合使用:当你申请YAGNI时,你要等到实际发生了一些变化。如果发生这种情况,您应该能够清楚地看到从变更原因中推断出的责任。

这也意味着责任可以随着每个新要求和变化而发展。进一步思考SRP和YAGNI将为您提供灵活设计和架构思考的方法。

答案 10 :(得分:2)

我也一直试图让我的头脑the SOLID principles of OOD,特别是单一的责任原则,即SRP(作为旁注the podcast with Jeff Atwood, Joel Spolsky and "Uncle Bob"值得倾听)。对我来说最大的问题是:SOLID试图解决哪些问题?

OOP就是建模。 The main purpose of modeling是以一种允许我们理解并解决问题的方式提出问题。建模迫使我们专注于重要的细节。同时我们可以使用封装来隐藏“不重要”的细节,这样我们只需要在绝对必要时处理它们。

我想你应该问问自己:你的班级试图解决什么问题?解决这个问题所需的重要信息是否已浮出水面?是否隐藏了不重要的细节,以便您在绝对必要时只需要考虑它们?

考虑这些事情会使程序更容易理解,维护和扩展。我认为这是OOD和SOLID原则的核心,包括SRP。

答案 11 :(得分:1)

我想投入的另一条经验法则如下:

如果您觉得有必要在测试用例中编写某种类型的笛卡尔积分产品,或者您想要模拟该类的某些私有方法,则会违反单一责任。

我最近有以下方式: 我有一个协同程序的cetain抽象语法树,稍后会生成C语言。现在,将节点视为Sequence,Iteration和Action。序列链两个协同程序,迭代重复协程直到用户定义的条件为真,并且Action执行某个用户定义的操作。此外,可以使用代码块来注释Actions和Iterations,这些代码块定义了在协同程序前进时要评估的操作和条件。

有必要对所有这些代码块应用某种转换(对于那些感兴趣的人:我需要用实际的实现变量替换概念用户变量以防止变量冲突。那些了解lisp宏的人可以想到gensym在行动:))。因此,最简单的方法是访问者在内部知道操作,并在访问时在Action和Iteration的带注释的代码块上调用它们并遍历所有语法树节点。但是,在这种情况下,我不得不在我的testcode中为visitAction-Method和visitIteration-Method复制断言“应用转换”。换句话说,我必须检查产品测试案例的责任Traversion(== {遍历迭代,遍历动作,遍历序列})x转换(好吧,代码块转换,它爆炸成迭代转换和动作转换)。因此,我很想使用powermock来移除转换 - 方法并用一些'返回'替换它我被改造了!“;' - Stub。

但是,根据经验法则,我将类拆分为一个TreeModifier类,其中包含一个NodeModifier-instance,它提供了modifyIteration,modifySequence,modifyCodeblock等方法。因此,我可以轻松地测试遍历的责任,调用NodeModifier并重构树并分别测试代码块的实际修改,从而消除了对产品测试的需要,因为现在责任分离(进行遍历和重构)具体修改)。

值得注意的是,稍后我可以在各种其他转换中大量重用Tr​​eeModifier。 :)

答案 12 :(得分:0)

如果你发现扩展类功能的麻烦而不担心你最终会破坏其他东西,或者你不能使用类而不修改大量的选项来修改它的行为,就像你的课程做得太多了。

一旦我使用了具有方法“ZipAndClean”的遗留类,这显然是压缩和清理指定的文件夹......