我知道“班级有一个改变的理由”。现在,到底是什么?是否有一些气味/迹象可以说明该课程没有一个责任?或者,真正的答案是否可以隐藏在YAGNI中,并且只有在您的班级第一次更改时才重构一项责任?
答案 0 :(得分:24)
有许多明显的情况,例如CoffeeAndSoupFactory
。同一装置中的咖啡和汤会导致非常令人不快的结果。在此示例中,设备可能会分为HotWaterGenerator
和某种Stirrer
。然后可以从这些组件构建新的CoffeeFactory
和SoupFactory
,并且可以避免任何意外混合。
在更微妙的情况中,数据访问对象(DAO)和数据传输对象(DTO)之间的紧张关系非常普遍。 DAO与数据库通信,DTO可序列化,以便在进程和机器之间进行传输。通常DAO需要引用您的数据库框架,因此它们在富客户端上不可用,它们既没有安装数据库驱动程序,也没有访问数据库所需的权限。
类中的方法开始按功能区域分组(“这些是Coffee
方法,这些是Soup
方法”。
实施多个接口。
答案 1 :(得分:24)
写一个简短但准确的描述课程的内容。
如果描述中包含单词“and”,则需要将其拆分。
答案 2 :(得分:11)
嗯,这个原则是与一些盐一起使用......以避免类爆炸。
单一责任不会转化为单一方法类。它意味着存在的一个原因......对象为其客户提供的服务。
留在路上的好方法......使用对象作为人物隐喻......如果对象是一个人,我会要求他做这个?将该责任分配给相应的类。但是,您不会要求同一个人执行您的管理文件,计算工资,发放薪水和验证财务记录......为什么您希望单个对象执行所有这些操作? (如果一个类承担多重责任,只要它们都是相关且连贯的就可以了。)
答案 3 :(得分:9)
一种简单实用的方法来检查单一责任(不仅是类而且还有类的方法)是名称选择。当您设计一个类时,如果您轻松找到该类的名称,该名称确切地指定了它所定义的内容,那么您的方法就是正确的。 选择名称的难度几乎总是设计不良的症状。
答案 4 :(得分:5)
您班级中的方法应该具有凝聚力......它们应该协同工作并在内部使用相同的数据结构。如果你发现有太多的方法似乎没有完全相关,或者看起来在不同的事情上运作,那么你很可能没有一个好的单一责任。
通常很难初步找到责任,有时你需要在几个不同的上下文中使用该类,然后在开始看到区别时将类重构为两个类。有时您会发现这是因为您将抽象的具体概念混合在一起。它们往往更难以看到,而且,在不同的背景下使用将有助于澄清。
答案 5 :(得分:5)
显而易见的迹象是,当你的班级最终看起来像Big Ball of Mud时,这实际上与SRP(单一责任原则)相反。
基本上,所有对象的服务应该专注于执行单一责任,这意味着每当你的课程改变并添加一个不尊重它的服务时,你就知道你正在“偏离”“正确”的道路; )
原因通常是由于一些快速修复,匆忙添加到类中修复一些缺陷。因此,更改班级的原因通常是检测您是否要打破SRP的最佳标准。
答案 6 :(得分:2)
也许比其他气味更具技术性:
我还发现重构单一责任很难。当你最终解决这个问题时,类的不同职责将与客户端代码纠缠在一起,这使得很难在没有破坏其他东西的情况下将一件事情分解出来。我宁愿错误地站在“太少”而非“太多”的一边。
答案 7 :(得分:2)
如果您最终使用的MethodA
MemberA
和使用MethodB
的{{1}}且不是部分并发或版本控制的一部分方案,你可能违反了SRP。
如果你注意到你有一个只是委托调用很多其他类的类,你可能会陷入代理类地狱。如果您最终可以直接在特定类中使用特定类,那么最终会在任何地方实例化代理类,尤其如此。我见过很多这个。将MemberB
和ProgramNameBL
类视为使用存储库模式的替代。
答案 8 :(得分:2)
以下是帮助我弄清楚我的班级是否违反SRP的一些事情:
答案 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并重构树并分别测试代码块的实际修改,从而消除了对产品测试的需要,因为现在责任分离(进行遍历和重构)具体修改)。
值得注意的是,稍后我可以在各种其他转换中大量重用TreeModifier。 :)
答案 12 :(得分:0)
如果你发现扩展类功能的麻烦而不担心你最终会破坏其他东西,或者你不能使用类而不修改大量的选项来修改它的行为,就像你的课程做得太多了。
一旦我使用了具有方法“ZipAndClean”的遗留类,这显然是压缩和清理指定的文件夹......