25

在 SRP 中,“责任”通常被描述为“改变的理由”,因此每个类(或对象?)应该只有一个理由,有人应该去那里改变它。

但是如果你把它带到极端的细粒度上,你可以说一个对象将两个数字加在一起是一种责任,也是改变的可能原因。因此对象不应该包含其他逻辑,因为它会产生另一个变化的原因。

我很好奇是否有人有任何“范围界定”策略,单一责任原则稍微不那么客观?

4

6 回答 6

30

这取决于您正在建模的内容。我已经就 SOLID 原则进行了一些广泛的写作和介绍,并在我对单一职责的讨论中专门解决了您的问题。

以下内容首次出现在 Code Magazine 2010 年 1 月/2 月号,可在线获取“SOLID 软件开发,一次一步”


单一职责原则说,一个类应该有一个,而且只有一个,改变的理由。

乍一看,这似乎违反直觉。说一个类应该只有一个存在的理由不是更容易吗?实际上,没有一个存在的理由可以很容易地被带到一个弊大于利的极端。如果你把它带到极端并构建有一个存在理由的类,你可能最终每个类只有一个方法。即使是最简单的流程,这也会导致大量的类蔓延,导致系统难以理解和难以更改。

一个类应该有一个改变的理由,而不是一个存在的理由,是您构建系统的业务环境。即使两个概念在逻辑上是不同的,但需要它们的业务环境可能需要它们成为一个且相同的概念。决定一个类何时应该更改的关键点不是基于概念的纯粹逻辑分离,而是基于业务对概念的感知。当业务感知和环境发生变化时,您就有理由更改课程。要了解单个类应该承担什么责任,您需要首先了解该类应该封装什么概念,以及您希望该概念的实现细节在哪里发生变化。

例如,考虑汽车中的发动机。你关心发动机的内部工作吗?您是否关心您有特定尺寸的活塞、凸轮轴、喷油器等?或者,您只关心上车时发动机是否按预期运行?当然,答案完全取决于您需要使用引擎的上下文。

如果您是在汽车店工作的机械师,您可能会关心发动机的内部工作原理。您需要了解发动机的具体型号、各种零件尺寸和其他规格。如果您没有这些信息,您可能无法正确地维修发动机。但是,如果您是一个普通的普通人,只需要从 A 点到 B 点的交通工具,那么您可能不需要那种级别的信息。单个活塞、火花塞、皮带轮、皮带等的概念对您来说几乎毫无意义。您只关心您驾驶的汽车是否有引擎并且它是否正常运行。

引擎示例直奔单一职责原则的核心。驾驶汽车与维修发动机的环境提供了两种不同的概念,即什么应该和不应该是一个单一的概念——改变的原因。在维修发动机的情况下,每个单独的部件都需要分开。您需要将它们编码为单个类,并确保它们都符合各自的规范。然而,在驾驶汽车的情况下,发动机是一个单一的概念,不需要进一步分解。在这种情况下,您可能有一个名为 Engine 的类。在这两种情况下,上下文已经确定了适当的职责分离是什么。

于 2010-03-17T14:52:40.917 回答
4

我不认为执行像将两个数字相加这样的任务是一种责任。责任有不同的形式和大小,但它们当然应该被视为比执行单一功能更大的事情。

为了更好地理解这一点,明确区分类负责什么和方法做什么可能会有所帮助。一个方法应该“只做一件事”(例如,添加两个数字,尽管在大多数情况下,'+' 是一种已经这样做的方法),而一个类应该向它的消费者呈现一个明确的“责任”。它的责任比方法要高得多。

像 Repository 这样的类具有明确而独特的职责。它有多种方法,如 Save 和 Load,但明确的责任是为 Person 实体提供持久性支持。一个类也可以协调和/或抽象依赖类的职责,再次将其作为单一职责呈现给其他消费类。

底线是,如果 SRP 的应用导致单方法类的全部目的似乎只是将该方法的功能包装在一个类中,那么 SRP 没有被正确应用。

于 2010-03-16T17:07:10.217 回答
4

我倾向于考虑业务需求的“变化速度”而不是“变化的原因”。

问题确实是事物一起改变的可能性有多大,而不是它们是否会改变。

差异是微妙的,但对我有帮助。让我们考虑一下wikipedia上关于报告引擎的示例:

  • 如果报告的内容和模板同时发生变化的可能性很高,那么它可以是一个组件,因为它们显然是相关的。(也可以是两个)

  • 但是,如果在没有模板的情况下内容更改的可能性很重要,那么它必须是两个组件,因为它们不相关。(有一个会很危险)

但我知道这是对 SRP 的个人解释。

另外,我喜欢的第二种技巧是:“用一句话描述你的课程”。它通常可以帮助我确定是否有明确的责任。

于 2010-03-16T16:44:10.387 回答
3

@Derick bailey:很好的解释
一些补充:
SRP 的应用是上下文基础是完全可以接受的。
问题仍然存在:是否有任何客观的方法来定义给定类是否违反 SRP?

一些设计上下文非常明显(例如 Derick 的汽车示例),但在其他情况下,必须定义类的行为的上下文多次保持模糊。

对于这种情况,如果通过将其职责拆分为不同的类,然后测量由于拆分而​​产生的新行为和结构关系的影响来分析模糊类行为,这可能会很有帮助。

一旦拆分完成,保留拆分的职责或将它们反向合并为单一职责的原因立即变得显而易见。

我已经应用了这种方法,并为我带来了良好的结果。

但我仍在继续寻找“定义类责任的客观方法”。

于 2013-12-13T12:59:01.487 回答
3

我使用的一个简单的经验法则是:责任的级别或粒度应该与所讨论的“实体”的级别或粒度相匹配。显然,方法的目的总是比类、服务或组件的目的更精确。

评估责任水平的一个好的策略是使用适当的比喻。如果您可以将您正在做的事情与现实世界中存在的事情联系起来,它可以帮助您从另一个角度看待您正在尝试解决的问题——包括能够识别适当的抽象级别和责任。

于 2010-03-16T20:29:21.347 回答
2

当 Chris Nicola 上面所说的“一个班级应该向它的消费者呈现一个单一的明确的“责任”时,我尊重我不同意

我认为 SRP 是关于在课堂上进行良好的设计,而不是课堂上的客户。

对我来说,责任是什么并不是很清楚,证明是这个概念出现的问题的数量。

“改变的唯一理由”

或者

“如果描述中包含“和”这个词,那么它需要被拆分”

引出一个问题:极限在哪里?最后,任何具有 2 个公共方法的类都有 2 个改变的理由,不是吗?

对我来说,真正的 SRP 导致了 Facade 模式,在这种模式下,你有一个简单地将调用委托给其他类的类

例如:

class Modem
  send()
  receive()

Refactors to ==>

class ModemSender
class ModelReceiver

+

class Modem
  send() -> ModemSender.send()
  receive()  -> ModemReceiver.receive()

欢迎意见

于 2011-05-30T11:32:47.263 回答