我知道“类只有单一责任原则”。那到底是什么?有哪些迹象可以告诉我们,这个类不仅仅承担了一个职责?或者真正的答案是隐藏在YAGNI中,只有当你的类第一次发生变化时才重构为单一职责?
我知道“类只有单一责任原则”。那到底是什么?有哪些迹象可以告诉我们,这个类不仅仅承担了一个职责?或者真正的答案是隐藏在YAGNI中,只有当你的类第一次发生变化时才重构为单一职责?
有许多明显的例子,例如 CoffeeAndSoupFactory
。在同一个容器中放入咖啡和汤可能会导致非常不好的结果。在这个例子中,容器可以被分解为 HotWaterGenerator
和某种类型的 Stirrer
。然后可以从这些组件构建新的 CoffeeFactory
和 SoupFactory
,避免任何意外的混合。
在更微妙的情况下,数据访问对象(DAO)和数据传输对象(DTO)之间的紧张关系非常普遍。 DAO 与数据库对话,DTO 可序列化以在进程和计算机之间传输。通常,DAO 需要引用您的数据库框架,因此它们无法在既没有安装数据库驱动程序也没有必要权限访问数据库的富客户端上使用。
一个类中的方法开始按功能区域进行分组("这些是咖啡方法,这些是汤方法")。
实现许多接口。
写一个简短但准确的描述该类的功能。
如果描述中包含单词 "and",则需要进行拆分。
好的,这个原则要加一些限制条件...以避免类爆炸。
单一职责并不意味着每个方法都应该独立成类。它是指一个对象存在的单一原因...为其客户提供的一项服务。
保持正确的编程方向的好方法... 就是将对象视为人的隐喻... 如果这个对象是一个人,我会找谁来完成这个任务?将该职责分配给相应的类。但是您不会让同一个人处理文件管理、计算工资、发放薪水和核实财务记录... 您为什么想让一个对象承担所有这些任务呢?(如果一个类承担多个相关且连贯的职责,那么也可以接受)
一个简单实用的检查单一职责原则(不仅仅是类,还包括类中的方法)的方法是选择名称。当您设计一个类时,如果您能够轻松地为该类找到一个能够准确指定其定义的名称,那么您就是朝着正确的方向前进了。
选择名称时遇到的困难几乎总是糟糕设计的症状。
你的类中的方法应该具有内聚性...它们应该共同工作并在内部使用相同的数据结构。如果你发现有太多似乎不完全相关或似乎在操作不同事物的方法,那么很可能你没有一个好的单一职责。
通常,最初找到职责是很困难的,有时你需要在几个不同的上下文中使用类,然后将类重构为两个类,从而开始看到区别。有时你会发现这是因为你将抽象和具体概念混合在一起。它们往往更难看出来,再次使用不同的上下文将有助于澄清。
Martin的C#中的敏捷原则、模式和实践帮助我很好地掌握了SRP。他将SRP定义为:
一个类应该只有一个改变的原因。
那么是什么驱动着变化呢?
Martin的答案是:
[...]每个职责都是一个变化的轴。(第116页)
并且进一步解释道:
在SRP的背景下,我们将职责定义为变更的原因。如果您可以想到多个更改类的动机,则该类具有多个职责(第117页)。
事实上,SRP是封装变化的。如果发生变化,它应该只对局部产生影响。
那么YAGNI在哪里呢?
YAGNI可以与SRP很好地结合使用:当您应用YAGNI时,您会等待直到实际发生某些更改。如果发生这种情况,则应能清楚地看到从更改原因推导出的职责。我一直在努力理解面向对象设计的SOLID原则,特别是单一职责原则,也就是SRP(顺便说一句,与Jeff Atwood、Joel Spolsky和“Uncle Bob”合作的播客值得一听)。对我来说最重要的问题是:SOLID试图解决什么问题?
OOP的全部内容都是关于建模。建模的主要目的是以一种使我们能够理解并解决问题的方式呈现问题。建模迫使我们专注于重要的细节。同时,我们可以使用封装来隐藏“不重要”的细节,这样我们只需在绝对必要时才必须处理它们。
我想你应该问自己:你的类正在尝试解决什么问题?你需要解决这个问题的重要信息是否已经浮出水面?不重要的细节是否被隐藏起来,这样你只需要在绝对必要时才考虑它们?
思考这些事情会导致更易于理解、维护和扩展的程序。我认为这是面向对象设计(OOD)和SOLID原则,包括SRP的核心。或许比其他代码更加技术化的一些警示:
我也发现将类重构为单一职责很难。当你最终开始进行时,类的不同职责已经交织在客户端代码中,使得在不破坏其他东西的情况下分离出一个内容变得困难。 我更喜欢自己犯“太少”的错误而不是“太多”的错误。
MemberA
的MethodA
和使用MemberB
的MethodB
,并且它不是某种并发或版本控制方案的一部分,那么你可能正在违反SRP原则。ProgramNameBL
和ProgramNameDAL
类视为使用存储库模式的替代方案。